在学习 C++ 中的过程中,找个算法作为练习。
仅供参考。
跳表原理
跳表原理讲解请参考 https://lotabout.me/2018/skip-list/
为了节约时间,这里只是简单说明,原文如上。
跳表(skip list) 对标的是平衡树(AVL Tree),是一种 插入/删除/搜索 都是 O ( l o g n ) O(log n) O(logn) 的数据结构。它最大的优势是原理简单、容易实现、方便扩展、效率更高。因此在一些热门的项目里用来替代平衡树,如 redis, leveldb 等。
跳表顾名思义就是跳跃的表格,理解起来其实就是跳着插入或者搜索。具体什么意思呢,其实就像是二分搜索一样,每次都将数组分成两点分,先定位搜索或者插入的数据在哪一部分,就可以节约搜索的时间。跳表其实是一样的原理,即建立多层索引(多层链表)。如果每次都是以二等分来建立索引的话,即如下图所示:
但是上述结构是“静态”的,即我们先拥有了一个链表,再在之上建了多层的索引。但是在实际使用中,我们的链表是通过多次插入/删除形成的,换句话说是“动态”的。上述的结构要求上层相邻节点与对应下层节点间的个数比是 1:2,随意插入/删除一个节点,这个要求就被被破坏了。
因此跳表(skip list)表示,我们就不强制要求 1:2 了,一个节点要不要被索引,建几层的索引,都在节点插入时由抛硬币决定。当然,虽然索引的节点、索引的层数是随机的,为了保证搜索的效率,要大致保证每层的节点数目与上节的结构相当。下面是一个随机生成的跳表:
对于上述随机跳表而言,每次插入一个新结点的时候,该结点的索引层数是抛硬币决定的,即由随机算法决定的。
当然为了防止运气太好,层数太高,我们一般会设置一个最大的层数 M a x L e v e l MaxLevel MaxLevel. 一般 M a x L e v e l = l o g 1 / p n MaxLevel=log_{1/p}n MaxLevel=log1/pn。 p p p 为概率。
跳表 c++ 实现
思路:
- 首先创建一个结点的结构体,结构体里包含 一个键值对,key 用来建立索引,而 value 则用于存储真正的值;指向下一个结点的指针 next,以及标志每个结点索引层数的参数 level. next 指针是一个数组,用于存储结点在所有层数上的下一个结点。 例如, 上述图中结点6的next指针为{NIL, 25, 9, 7}.
- 创建class skipList, 包含头结点,尾结点,list 的最大层数,随机层数方法,以及一些列操作。
- 头结点和尾节点分别为整型的最小值和最大值,并在初始化时让所有层数上的头结点都指向尾节点。
- 操作包含插入,查找,删除。
SkipNode
为了可以适用于任何类型的value, 这里用了 template
template<typename T>
struct SkipNode
{
int key;
T value;
vector<SkipNode*> next;
SkipNode(int k, T v, int level);
};
//构造函数,初始化
template<typename T> SkipNode<T>::SkipNode(int k, T v, int level)
: key(k), value(v)
{
for (int i = 0; i < level; i++)
{
next.push_back(nullptr);
}
}
SkipList
template<class T>
class SkipList
{
public:
//头结点
SkipNode<T>* head;
//列表最大层数
int maxLevel;
//整型的最小值和最大值
const int minInt = numeric_limits<int>::min();
const int maxInt = numeric_limits<int>::max();
public:
//构造函数
SkipList(int maxLevel, T iniValue);
//析构函数
~SkipList();
//随机层数方法
int randomLevel();
//插入, 查找, 删除
SkipNode<T>* insert(int k, T v);
SkipNode<T>* find(int k);
SkipNode<T>* deleteNode(int k);
//打印
void printNode();
private:
//尾节点
SkipNode<T>* tail;
//找到当前列表或者node的最大层数
int nodeLevel(vector<SkipNode<T>*> p);
};
//初始化
template<class T> SkipList<T>::SkipList(int maxLevel, T iniValue)
: maxLevel(maxLevel)
{
//初始化头结点和尾节点为整型最小值和最大值
head =