C++实现跳表:四、跳表的插入
本文将正式介绍跳表的创建和搜索、插入操作几个核心部分:
1. 创建跳表
下面是跳表的基础代码框架,仅仅包括本节内容中必要的类定义和成员函数,其余内容为了保证篇幅长度均已省略:
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cstring>
// 节点类定义
template <typename K, typename V>
class Node {
// 节点类成员变量和方法(省略)
};
// 跳表类定义
template <typename K, typename V>
class SkipList {
public:
SkipList(int); // 构造函数
~SkipList(); // 析构函数
int get_random_level(); // 生成随机层级
Node<K, V> *create_node(K, V, int); // 创建新节点
int insert_element(K, V); // 插入元素
bool search_element(K); // 搜索元素
// 其他成员变量和函数(省略)
private:
int _max_level; // 最大层级
int _skip_list_level; // 当前层级
Node<K, V> *_header; // 头节点
// 其他私有成员(省略)
int _element_count; // 节点数量
};
跳表的构造函数负责初始化跳表,主要包括以下步骤:
- 设置最大层级:根据预设值确定跳表的最大层级
- 初始化成员变量:设置跳表当前层级为 0,节点计数为 0
- 创建头节点:初始化一个头节点,其层级等于跳表的最大层级
具体代码如下:
template <typename K, typename V>
SkipList<K, V>::SkipList(int max_level) {
this->_max_level = max_level; // 设置最大层级
this->_skip_list_level = 0; // 初始化当前层级为 0
this->_element_count = 0; // 初始化节点计数为 0
K k; // 默认键
V v; // 默认值
// 创建头节点,并初始化键值为默认值
this->_header = new Node<K, V>(k, v, _max_level);
};
完成跳表的初始化之后,接下来的环节是实现节点创建的方法。
创建新节点的过程涉及以下主要步骤:
- 节点实例化:依据提供的键(k)和值(v),创建一个新的节点实例。同时,为这个新节点指定一个层级(level),这一层级决定了节点在跳表中的高度
- 返回新节点:完成节点的创建后,返回这个新创建的节点实例,以便于进一步在跳表中进行插入操作。
具体代码如下:
template <typename K, typename V>
Node<K, V> *SkipList<K, V>::create_node(const K k, const V v, int level) {
// 实例化新节点,并为其分配指定的键、值和层级
Node<K, V> *n = new Node<K, V>(k, v, level);
return n; // 返回新创建的节点
}
2. 实现搜索方法
2.1 理论基础
完成跳表的创建之后,让我们先了解跳表的搜索方法,因为后续的插入方法和删除方法都依赖于搜索方法。
我们之前已经简要介绍过跳表的搜索机制。搜索开始于跳表的顶层,这一点在我们的 SkipList 类中通过变量 _skip_list_level 得到体现,该变量记录了跳表当前的最高层级。
并且在 “跳表的定义” 章节中,我们曾经介绍过,每个节点都维护一个 forward 数组,该数组记录了该节点在每一层的下一个节点的指针。
_header 作为跳表的头节点,为操作跳表提供了一个统一的入口。跳表的本质是由原始链表经过筛选部分节点构建成的多级索引链表。因此,跳表可视为多个层级的单链表组合而成。在单链表结构中,通常会有一个头节点,其 next 指针指向链表的第一个实际节点。相应地,对于多层级的跳表结构,我们需要多个头节点来指向各层的第一个节点。这些头节点被存储在 _header 节点的 forward 数组中。例如,_header->forward[0] 指向最底层的第一个节点,_header->forward[1] 指向第二层的第一个节点,依此类推。
基于这个结构,利用 _header 节点和 _skip_list_level(记录跳表实际最高层级的变量)作为起点,我们可以从跳表的最顶层开始进行搜索。
2.2 代码实现
以下是拥有详细注释的搜索方法代码:
template <typename K, typename V>
bool SkipList<K, V>::search_element(K key) {
// 定义一个指针 current,初始化为跳表的头节点 _header
Node<K, V> *current = _header;
// 从跳表的最高层开始搜索
for (int i = _skip_list_level; i >= 0; i--) {
// 遍历当前层级,直到下一个节点的键值大于或等于待查找的键值
while (current->forward[i] && current->forward[i]->get_key() < key) {
// 移动到当前层的下一个节点
current = current->forward[i];
}
// 当前节点的下一个节点的键值大于待查找的键值时,进行下沉到下一层
// 下沉操作通过循环的 i-- 实现
}
// 检查当前层(最底层)的下一个节点的键值是否为待查找的键值
current = current->forward[0];
if (current && current->get_key() == key) {
// 如果找到匹配的键值,返回 true
return true;
}
// 如果没有找到匹配的键值,返回 false
return false;
}
3. 实现插入方法
3.1 理论基础
继搜索节点的逻辑之后,我们现在转向如何在跳表中插入新节点。
插入过程主要涉及三个关键步骤:
1、确定节点层级
首先,我们需要为新插入的节点随机确定其所在的层级
2、寻找插入位置
通过之前讨论的搜索方法,我们能够定位到了新节点应当插入的具体位置
3、更新指针关系
最关键的步骤是在插入节点时更新各层的指针关系。
具体而言,这包括两个方面:
- 将新节点在各层的前驱节点(即在该层中小于新节点且最接近新节点的节点)的 forward 指针指向新节点。
- 同时,新节点的 forward 指针需要指向其在各层的前驱节点原本指向的节点。
此操作和单链表的插入操作类似,区别在于跳表需要在多层中的重复进行此操作,而链表只需要进行一次。
3.2 代码实现
在插入新节点前,我们首先需要定位插入位置,此过程与 search_element 函数的逻辑相似。下面的代码框架展示了如何执行这一操作:
template <typename K, typename V>
int SkipList<K, V>::insert_element(const K key, const V value) {
Node<K, V> *current = this->_header;
// 用于在各层更新指针的数组
Node<K, V> *update[_max_level + 1]; // 用于记录每层中待更新指针的节点
memset(update, 0, sizeof(Node<K, V> *) * (_max_level + 1));
// 从最高层向下搜索插入位置
for (int i = _skip_list_level; i >= 0; i--) {
// 寻找当前层中最接近且小于 key 的节点
while (current->forward[i] != NULL && current->forward[i]->get_key() < key) {
current = current->forward[i]; // 移动到下一节点
}
// 保存每层中该节点,以便后续插入时更新指针
update[i] = current;
}
// 移动到最底层的下一节点,准备插入操作
current = current->forward[0];
// 检查待插入的节点的键是否已存在
if (current != NULL && current->get_key() == key) {
// 键已存在,取消插入
return 1;
}
// 后续插入操作(略)
}
在这段代码中,Node<K, V>* update[_max_level + 1] 是用于实现插入节点的关键数据结构,它是一个节点指针数组,用于记录在上文中提到的,待插入节点的前驱节点(即在该层中小于新节点且最接近新节点的节点)。这个数组解决了之前提到的关键问题:在插入新节点时如何更新每层的指针关系。
通过内层的 while 循环,一旦发现 current->forward[i] 指向的节点的 key 值 > 待插入节点的 key,那么 current 就是待插入节点的前驱节点。而通过外层的 for 循环,我们可以寻找出待插入节点在不同层的所有前驱节点。
接下来的判断逻辑是为了确保不会插入重复的节点。如果 current 指向的节点的 key 与待插入的节点的 key 相等,说明跳表中已存在与待插入节点相同 key 的节点,此时我们只需要将该节点的 value 更新,并且返回 1;
继续深入跳表的插入逻辑,以下是插入操作的代码实现:
template <typename K, typename V>
int SkipList<K, V>::insert_element(const K key, const V value) {
// ...
// 检查待插入的节点是否已存在于跳表中
if (current == NULL || current->get_key() != key) {
// 通过随机函数决定新节点的层级高度
int random_level = get_random_level();
// 如果新节点的层级超出了跳表的当前最高层级
if (random_level > _skip_list_level) {
// 对所有新的更高层级,将头节点设置为它们的前驱节点
for (int i = _skip_list_level + 1; i <= random_level; i++) {
update[i] = _header;
}
// 更新跳表的当前最高层级为新节点的层级
_skip_list_level = random_level;
}
Node<K, V> *inserted_node = create_node(key, value, random_level);
// 在各层插入新节点,同时更新前驱节点的forward指针
for (int i = 0; i <= random_level; i++) {
// 新节点指向当前节点的下一个节点
inserted_node->forward[i] = update[i]->forward[i];
// 当前节点的下一个节点更新为新节点
update[i]->forward[i] = inserted_node;
}
_element_count++;
}
return 0;
}
当新插入节点的层级高于跳表当前层级时,我们需要在 update 数组中为这些新层级指定头节点(_header),因为这些层级在插入之前是不存在节点的。这样,新节点在这些高层级直接作为第一个节点。
新节点按照确定的层级被插入。对每一层,我们首先设置新节点的 forward 指针指向当前节点的下一个节点,然后更新当前节点的 forward 指针指向新节点。这一过程确保了新节点正确地被链入每一层。
通过这些步骤,我们不仅完成了新节点的插入操作,还确保了跳表结构的正确性和索引的有效维护。
3.3 完整代码与解释
template <typename K, typename V>
int SkipList<K, V>::insert_element(const K key, const V value) {
Node<K, V> *current = this->_header;
// 用于在各层更新指针的数组
Node<K, V> *update[_max_level + 1]; // 用于记录每层中待更新指针的节点
memset(update, 0, sizeof(Node<K, V> *) * (_max_level + 1));
// 从最高层向下搜索插入位置
for (int i = _skip_list_level; i >= 0; i--) {
// 寻找当前层中最接近且小于 key 的节点
while (current->forward[i] != NULL && current->forward[i]->get_key() < key) {
current = current->forward[i]; // 移动到下一节点
}
// 保存每层中该节点,以便后续插入时更新指针
update[i] = current;
}
// 移动到最底层的下一节点,准备插入操作
current = current->forward[0];
// 检查待插入的键是否已存在
if (current != NULL && current->get_key() == key) {
// 键已存在,取消插入
return 1;
}
// 检查待插入的键是否已存在于跳表中
if (current == NULL || current->get_key() != key) {
// 通过随机函数决定新节点的层级高度
int random_level = get_random_level();
// 如果新节点的层级超出了跳表的当前最高层级
if (random_level > _skip_list_level) {
// 对所有新的更高层级,将头节点设置为它们的前驱节点
for (int i = _skip_list_level + 1; i <= random_level; i++) {
update[i] = _header;
}
// 更新跳表的当前最高层级为新节点的层级
_skip_list_level = random_level;
}
Node<K, V> *inserted_node = create_node(key, value, random_level);
// 在各层插入新节点,同时更新前驱节点的 forward 指针
for (int i = 0; i <= random_level; i++) {
// 新节点指向当前节点的下一个节点
inserted_node->forward[i] = update[i]->forward[i];
// 当前节点的下一个节点更新为新节点
update[i]->forward[i] = inserted_node;
}
_element_count++;
}
return 0;
}
4.小结
实现了 SkipList 类中搜索节点和插入节点的成员函数:
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cstring>
template <typename K, typename V>
class Node {
public:
Node() {}
Node(K k, V v, int);
~Node();
K get_key() const;
V get_value() const;
void set_value(V);
Node<K, V> **forward;
int node_level;
private:
K key;
V value;
};
template <typename K, typename V>
Node<K, V>::Node(const K k, const V v, int level) {
this->key = k;
this->value = v;
this->node_level = level;
this->forward = new Node<K, V> *[level + 1];
memset(this->forward, 0, sizeof(Node<K, V> *) * (level + 1));
};
template <typename K, typename V>
Node<K, V>::~Node() {
delete[] forward;
};
template <typename K, typename V>
K Node<K, V>::get_key() const {
return key;
};
template <typename K, typename V>
V Node<K, V>::get_value() const {
return value;
};
template <typename K, typename V>
void Node<K, V>::set_value(V value) {
this->value = value;
};
// 跳表结构
template <typename K, typename V>
class SkipList {
public:
SkipList(int);
int get_random_level();
Node<K, V> *create_node(K, V, int);
int insert_element(K, V);
bool search_element(K);
private:
int _max_level;
int _skip_list_level;
Node<K, V> *_header;
int _element_count;
};
template <typename K, typename V>
Node<K, V> *SkipList<K, V>::create_node(const K k, const V v, int level) {
Node<K, V> *n = new Node<K, V>(k, v, level);
return n;
}
template <typename K, typename V>
int SkipList<K, V>::insert_element(const K key, const V value) {
Node<K, V> *current = this->_header;
Node<K, V> *update[_max_level + 1];
memset(update, 0, sizeof(Node<K, V> *) * (_max_level + 1));
for (int i = _skip_list_level; i >= 0; i--) {
while (current->forward[i] != NULL && current->forward[i]->get_key() < key) {
current = current->forward[i];
}
update[i] = current;
}
current = current->forward[0];
if (current != NULL && current->get_key() == key) {
return 1;
}
if (current == NULL || current->get_key() != key) {
int random_level = get_random_level();
if (random_level > _skip_list_level) {
for (int i = _skip_list_level + 1; i < random_level + 1; i++) {
update[i] = _header;
}
_skip_list_level = random_level;
}
Node<K, V> *inserted_node = create_node(key, value, random_level);
for (int i = 0; i <= random_level; i++) {
inserted_node->forward[i] = update[i]->forward[i];
update[i]->forward[i] = inserted_node;
}
_element_count++;
}
return 0;
}
template <typename K, typename V>
bool SkipList<K, V>::search_element(K key) {
Node<K, V> *current = _header;
for (int i = _skip_list_level; i >= 0; i--)
{
while (current->forward[i] && current->forward[i]->get_key() < key) {
current = current->forward[i];
}
}
current = current->forward[0];
if (current and current->get_key() == key) {
return true;
}
return false;
}
template <typename K, typename V>
SkipList<K, V>::SkipList(int max_level) {
this->_max_level = max_level;
this->_skip_list_level = 0;
this->_element_count = 0;
K k;
V v;
this->_header = new Node<K, V>(k, v, _max_level);
};
template<typename K, typename V>
int SkipList<K, V>::get_random_level(){
int k = 1;
while (rand() % 2) {
k++;
}
k = (k < _max_level) ? k : _max_level;
return k;
};
int main() {
int N;
int M;
SkipList<int, int> *skip_list = new SkipList<int, int>(16);
std::cin >> N >> M;
for (int i = 0; i < N; i++) {
int key;
int value;
std::cin >> key >> value;
if (skip_list->insert_element(key, value) == 0) {
std::cout << "Insert Success" << std::endl;
} else {
std::cout << "Insert Failed" << std::endl;
}
}
// 搜索
for (int i = 0; i < M; i++) {
int key;
std::cin >> key;
if (skip_list->search_element(key)) {
std::cout << "Search Success" << std::endl;
} else {
std::cout << "Search Failed" << std::endl;
}
}
return 0;
}
原文地址:https://blog.csdn.net/qq_72642900/article/details/140498212
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!