一、概念
跳跃表是一种基于有序链表的扩展,简称跳表,其就是使用关键节点作为索引的一种数据结构。
可以通过以下方式更快查找到有序链表的某一节点:
利用类似索引的思想,提取出链表中的部分关键节点。比如,给定一个长度为7的有序链表,节点值依次是1->2->3->5->6->7->8,那么我们可以取出所有值为奇数节点的作为关键点。
此时如果要插入一个值是4的新节点,不再需要和原节点8,7,6,5,3逐一比较,只需要比较关键节点8,5,3.
确定了新节点在关键节点中的位置(3和5之间),就可以回到原链表,迅速定位到对应的位置,然后进行插入。
多层关键节点多层索引
对于跳跃表来说,当然不能只会有一层索引节点,那么可以进一步提取索引,在索引层中提取出一层新的索引。
有了二级索引之后,新的节点可以先和2级索引进行比较,确定大体范围;然后再和1级索引比较;最后回到原链表,找到并插入对应位置。
层级极限是什么?
当节点足够多的时候,不止能提取出二级索引,还可以向高层次提取,保证每一层是上一层节点数的一半。
提取的极限:同一层只有两个节点的时候,因为一个节点没有比较的意义。
怎么从新节点当中选取一部分提到上一层?
当大量的新节点通过逐层比较,最终插入到原链表之后,上层的索引节点会变得不够用。这时候需要从新节点当中选取一部分提到上一层。
使用随机决定新节点是否提升层级,每次向上提升一层的概率是百分之50.
为什么要随机决定新节点是否层级提升?
(1)跳跃表删除和添加节点是不可预测的,很难用一种有效地算法来保证跳跃表的索引部分始终均匀。
(2)采用随机法虽然不能保证索引绝对均匀分布,却可以让大体趋于均匀。
二、时间复杂度分析
跳跃表将新节点(删除节点)和各层索引节点逐一进行比较,确定原链表的插入或者删除位置。时间复杂度为O(logn)。
跳跃表的插入和删除操作需要的时间复杂度和原始链表一样,都为O(1)。
三、跳跃表的特征
一个完整的跳跃表,应该具有以下特征:
1、一个跳表应该有几个层(level)组成。
2、跳表的第一层包含所有的元素。
3、每一层都是一个有序的链表。
4、如果元素x出现在第i层,则所有比i小的层都包含x。
5、每个节点包含key及其对应的value和一个指向同一层链表的下个节点的指针数组(next[i])。
四、跳跃表和红黑树性能比较
目前,经常使用的平衡数据结构有:B树、红黑树、AVL树等。跳跃表是平衡树一种替代的数据结构,但是和红黑树不相同的是,跳跃表对于平衡的实现是基于一种随机化的算法,跳跃表的插入和删除工作是比较简单的。
1、查询性能:
跳跃表和红黑树的查询时间复杂度都为O(logn)。
2、插入和删除性能:
跳跃表在进行插入和删除操作时,相对来说,更容易调整结构,也就是可能需要在索引层中增加或者删除相应节点。
红黑树在进行插入和删除操作时,就需要进行旋转或者颜色调度操作,相对复杂一些。
3、空间复杂度:
跳跃表的空间复杂度相对高一些,需要额外的空间来存储多层指针。平均情况下O(n),最糟糕的情况下O(nlogn)。
红黑树空间开销相对跳跃表来说会低。O(n)。
4、应用场景:
如果要在高并发的场景下,频繁进行插入和删除节点,那么跳跃表可能更合适一些。
如果对空间性能要求较高,那么可以去使用红黑树。
五、代码实现
#include<iostream>
#include<time.h>
#include<assert.h>
#include<stdlib.h>
using namespace std;
#define MAX_L 5//跳跃表最大层数
//节点结构
struct Node
{
int key;//节点的值
Node* next[];//多层链表节点
};
//跳跃表结构
struct Skiplist
{
int level;//跳跃表层数
Node* head;
};
//创建节点
Node* CreateNode(int level, int key)
{
Node* p = (Node*)malloc(sizeof(Node) + level * sizeof(Node*));
if (nullptr == p)
{
return nullptr;
}
for (int i = 0; i < level; ++i)
{
p->next[i] = nullptr;
}
p->key = key;
return p;
}
//创建表
Skiplist* CreateList()
{
Skiplist* s = (Skiplist*)malloc(sizeof(Skiplist));
if (nullptr == s)
{
return nullptr;
}
s->level = 0;
Node* nh = CreateNode(MAX_L + 1, 0);
if (nullptr == nh)
{
free(s);
return nullptr;
}
s->head = nh;
return s;
}
//生成新节点的级数
int random()
{
int level = 0;
while (level <= MAX_L && rand() < RAND_MAX / 2)
{
++level;
}
return level;
}
//向跳跃表中插入元素
bool InsertNode(Skiplist* s, int key)
{
if (nullptr == s) return false;
//第一步,查找到在每层待插入位置,更新update数组
Node* update[MAX_L + 1] = {};
Node* q = nullptr, * p = s->head;
//找到每一层插入前的一个节点,更新update数组
for (int i = s->level; i >= 0; --i)
{
while (p->next[i] != nullptr && p->next[i]->key < key)
{
p = p->next[i];
}
update[i] = p;
}
p = p->next[0];
if (p != nullptr && p->key == key)
{
return true;
}
//第二步,随机产生一个层数
int newlevel = random();
//新产生的节点层数比原跳跃表大
if (newlevel > s->level)
{
for (int i = s->level + 1; i <= newlevel; ++i)
{
update[i] = s->head;
}
s->level = newlevel;
}
//第三步,从高往下插入
p = CreateNode(newlevel + 1, key);
if (nullptr == p)
{
return false;
}
//根据update数组在每一层中插入新节点
for (int i = newlevel; i >= 0; --i)
{
p->next[i] = update[i]->next[i];
update[i]->next[i] = p;
}
return true;
}
//删除跳跃表中的元素
bool DeleteNode(Skiplist* s, int key)
{
Node* update[MAX_L + 1] = {};
Node * p = s->head;
//找到每一层待删除元素前的一个节点,放入update数组中
for (int i = s->level; i >= 0; --i)
{
while (p->next[i] != nullptr && p->next[i]->key < key)
{
p = p->next[i];
}
update[i] = p;
}
Node* q = p->next[0];
assert(q != nullptr);
//判断q是否为待删除的节点
if (q==nullptr && q->key != key)
{
return false;
}
for (int i = s->level; i >= 0; --i)
{
if (update[i]->next[i]==q)
{
update[i]->next[i] = q->next[i];
if (s->head->next[i] == nullptr)
{
s->level--;
}
}
}
free(q);
return true;
}
void Print(Skiplist* s)
{
Node* p = nullptr;
for (int i = s->level; i >= 0; --i)
{
p = s->head->next[i];
cout << "level:" << i << endl;
while (p != nullptr)
{
cout << "key:" << p->key << " ";
p = p->next[i];
}
cout << endl;
}
}
void FreeList(Skiplist* s)
{
if (nullptr == s)
{
return;
}
Node* q = s->head;
Node* next = nullptr;
while (q != nullptr)
{
next = q->next[0];
free(q);
q = next;
}
free(s);
}
int main()
{
Skiplist* mylist = CreateList();
for (int i = 1; i < 5; ++i)
{
InsertNode(mylist, i);
}
DeleteNode(mylist, 3);
Print(mylist);
free(mylist);
return 0;
}