参考文章
知乎—跳表这种高效的数据结构,值得每一个程序员掌握—somenzz
跳表是什么?
顾名思义,跳表即跳跃的链表。其添加元素、删除元素、查找元素的复杂度都为 O ( l g n ) O(lgn) O(lgn)。
核心思想
类似倍增思想(其实是倍减?orz),层层向上构建索引,再具体一点就是,假设一级索引就是普通链表,二级索引就每两个元素取一个构建链表,三级索引就是每四个元素取一个构建链表……也就是说第 i i i级索引的元素个数是第 i − 1 i-1 i−1级索引的元素个数的一半。看图会更好理解一点:
t i p s : tips: tips:这个知乎作者写的真的不错,强烈推荐……若侵权请私聊我删除。
此时我们查询一个元素完全可以从顶层索引开始查询,查询路径一定从左上角开始,落到最后一层,且每一步只能向右或向下。
时间复杂度
显然跳表的高度是 O ( l g n ) O(lgn) O(lgn),假设每一层最多走 m m m个节点,显然查询复杂度为 O ( m ∗ l g n ) O(m*lgn) O(m∗lgn)。而我们从跳表的构造方式不难发现 m < = 3 m<=3 m<=3,所以复杂度为 O ( l g n ) O(lgn) O(lgn)。严格证明的话我也不太会……从二进制表示上来理解可能也是不错的角度。
空间复杂度
一般来说最高级的索引至少留2个,因为1个没有意义。每一层加起来就是个等比数列嘛,空间复杂度还是 O ( n ) O(n) O(n)的。实际构建跳表时,我们不会copy节点,只会存储其索引也就是地址,所以额外占用的空间其实不会很多。
实现
要求插入、删除、查询复杂度在 O ( l g n ) O(lgn) O(lgn),可能会有重复元素。方便起见,我们把相等的数据合并到一起,引入一个 c o u n t count count成员记录相等的元素个数。
跳表节点的数据结构
class Listnode
{
public:
//val-值 count-出现的次数
int val,count;
//forwards[i]代表在第i+1级(level)索引下 该节点的下一节点
//1代表最低级索引 相当于普通链表
//这里还有一个隐含信息:该节点是第forwards.size() 级的节点
vector<Listnode*> forwards;
Listnode(int v=-1,int level=0):val(v),count(1),forwards(level,nullptr){
}
int get_level(){
return forwards.size(); }
};
跳表的数据结构
class Skiplist {
public:
//限制跳表的最大level
const int max_level=16;
//目前最高level
int cur_level;
//跳表头节点
Listnode *head;
}
构造函数
Skiplist():head(new Listnode(-1,max_level)) {
}
头节点的 v a l u e value value没有意义,反正也不会用来比较。
析构函数
~Skiplist() {
delete head; }
random_level
主要用于为新增节点决定索引级别,这里的实现方式挺多的,不过基本都是基于随机数的。只要保证一个新增节点位于第 i i i级的概率为 1 / 2 i − 1 1/2^{i-1} 1/2i−1即可(不太严谨,别太在意)。
//得到一个level
int random_level()
{
int level=1;
while((rand()&1)&&level<max_level)
++level;
return level;
}
查询操作
这个还是比较简单的吧,从最高级索引(当前)开始查询即可,为了方便其他操作,可以写出几个功能类似的函数。
//查找指定节点
Listnode* _search(int target)
{
//当前节点 下一节点
Listnode *cur=head,*nxt;
for(int i=cur_level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt->val<target)
cur=nxt;
//找到 返回其前驱
if(nxt&&nxt->val==target)
return nxt;
}
return nullptr;
}
//查找指定节点的前驱 为了便于获得后继 需要level记录层级
Listnode* _search_before(int target,int &level)
{
//当前节点 下一节点
Listnode *cur=head,*nxt;
for(int i=cur_level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt->val<target)
cur=nxt;
//找到 返回其前驱 注意:nxt=cur->forwards[level]
if(nxt&&nxt->val==target)
{
level=i;
return cur;
}
}
return nullptr;
}
//查找target是否存在
bool search(int target) {
return _search(target)!=nullptr;
}
添加操作
由于允许重复元素,这里还是要分几种情况的。假设要插入的 n u m num num已经存在,那么递增其计数即可,否则要在跳表中新增一个节点,这种情况就比较麻烦了,我们要按照以下步骤进行:
- 构建新增节点,并决定它所位于的索引 i i i。这里多说一句,若一个元素位于第 i i i级索引,则其一定位于 < i <i <i的所有索引。
- 对于所有 < = i <=i <=i的索引 j j j,我们要找到一个这样的节点: n x t = c u r . f o r w a r d s [ j ] , c u r . v a l u e < n u m & & n x t . v a l u e > n u m nxt=cur.forwards[j],cur.value<num\ \ \&\&\ \ nxt.value>num nxt=cur.forwards[j],cur.value<num && nxt.value>num,然后在它们之间添加一个新的索引。
t i p s : tips: tips:一定要注意查询的起始索引,知乎那篇文章这里就有点问题,他直接从新增节点的层级开始查询了,导致复杂度大大增加。
//添加num到跳表中
void add(int num) {
//查找指定节点
Listnode *cur=_search(num);
//若存在 直接递增计数即可
if(cur)
++cur->count;
//否则需要在跳表中新增节点
else
{
//为新加节点随机一个level
int level=random_level();
if(cur_level<level)
cur_level=level;
//新节点
Listnode *newnode=new Listnode(num,level);
cur=head;
Listnode *nxt;
//注意此处循环要从cur_level开始 而不要从level开始
//因为level可能远小于cur_level 如果此时num是一个较大的数 会浪费很多时间
for(int i=cur_level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt->val<num)
cur=nxt;
//在第i+1级新增节点 注意i+1要<=level 即i<level
if(i<level)
{
newnode->forwards[i]=nxt;
cur->forwards[i]=newnode;
}
}
}
}
删除操作
同样需要分类讨论,若 n u m num num不存在,无需操作,否则递减其计数,若计数递减后等于0,需要把该节点从跳表中移除。不妨设待删节点为 d e l del del,其的层级为 i i i,那么我们找到所有 j < = i & & c u r . f o r w a r d s [ j ] = d e l j<=i\ \ \&\&\ \ cur.forwards[j]=del j<=i && cur.forwards[j]=del的节点,修改其下一个索引即可。最后别忘了回收空间。
//从跳表中删除num
bool erase(int num) {
int level;
//待删节点前驱
Listnode *cur=_search_before(num,level);
//若存在
if(cur)
{
Listnode *del=cur->forwards[level];
level=del->get_level();
//需要递减其count 若递减后count=0 需要从跳表中移除
if(--del->count==0)
{
Listnode *nxt;
for(int i=level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt!=del)
cur=nxt;
if(nxt)
cur->forwards[i]=nxt->forwards[i];
}
//回收空间
delete del;
}
return true;
}
return false;
}
完整代码
C + + : C++: C++:
class Listnode
{
public:
//val-值 count-出现的次数
int val,count;
//forwards[i]代表在第i级(level)的情况下 该节点的下一节点 1代表最低级 相当于普通链表\
//这里还有一个隐含信息:该节点是第forwards.size() 级的节点
vector<Listnode*> forwards;
Listnode(int v=-1,int level=0):val(v),count(1),forwards(level,nullptr){
}
int get_level(){
return forwards.size(); }
};
class Skiplist {
public:
//限制跳表的最大level
const int max_level=16;
//目前最高level
int cur_level;
//跳表头节点
Listnode *head;
Skiplist():head(new Listnode(-1,max_level)) {
}
~Skiplist() {
delete head; }
//得到一个level
int random_level()
{
int level=1;
while((rand()&1)&&level<max_level)
++level;
return level;
}
//查找指定节点
Listnode* _search(int target)
{
//当前节点 下一节点
Listnode *cur=head,*nxt;
for(int i=cur_level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt->val<target)
cur=nxt;
//找到 返回其前驱
if(nxt&&nxt->val==target)
return nxt;
}
return nullptr;
}
//查找指定节点的前驱 为了便于获得后继 需要level记录层级
Listnode* _search_before(int target,int &level)
{
//当前节点 下一节点
Listnode *cur=head,*nxt;
for(int i=cur_level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt->val<target)
cur=nxt;
//找到
if(nxt&&nxt->val==target)
{
level=i;
return cur;
}
}
return nullptr;
}
//查找target是否存在
bool search(int target) {
return _search(target)!=nullptr;
}
//添加num到跳表中
void add(int num) {
//查找指定节点
Listnode *cur=_search(num);
//若存在 直接递增计数即可
if(cur)
++cur->count;
//否则需要在跳表中新增节点
else
{
//为新加节点随机一个level
int level=random_level();
if(cur_level<level)
cur_level=level;
//新节点
Listnode *newnode=new Listnode(num,level);
cur=head;
Listnode *nxt;
//注意此处循环要从cur_level开始 而不要从level开始
//因为level可能远小于cur_level 如果此时num是一个较大的数 会浪费很多时间
for(int i=cur_level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt->val<num)
cur=nxt;
//在第i+1级新增节点 注意i+1要<=level 即i<level
if(i<level)
{
newnode->forwards[i]=nxt;
cur->forwards[i]=newnode;
}
}
}
}
//从跳表中删除num
bool erase(int num) {
int level;
//待删节点前驱
Listnode *cur=_search_before(num,level);
//若存在
if(cur)
{
Listnode *del=cur->forwards[level];
level=del->get_level();
//需要递减其count 若递减后count=0 需要从跳表中移除
if(--del->count==0)
{
Listnode *nxt;
for(int i=level-1;i>=0;i--)
{
while((nxt=cur->forwards[i])&&nxt!=del)
cur=nxt;
if(nxt)
cur->forwards[i]=nxt->forwards[i];
}
//回收空间
delete del;
}
return true;
}
return false;
}
};
p y t h o n 3 : python3: python3:
# 一个跳表节点
class listNode(object):
def __init__(self,value,level):
self.value=value
# 该值出现的次数
self.count=1
# forwards[i]代表在第i级(level)的情况下 该节点的下一节点 0代表最低级 相当于普通链表
self.forwards=[None]*level
def get_level(self) -> int:
return len(self.forwards)
class Skiplist:
def __init__(self):
self.max_level=16
self.cur_level=1
self.head=listNode(value=None,level=self.max_level)
def random_level(self,p=0.5) -> int:
level=1
while random.random()<p and level<self.max_level:
level+=1
return level
def _search(self,target: int) -> listNode:
cur=self.head
for i in range(self.cur_level-1,-1,-1):
while cur.forwards[i] and cur.forwards[i].value<target:
cur=cur.forwards[i]
if cur.forwards[i] and cur.forwards[i].value==target:
return cur.forwards[i]
return None
def _search_before(self,target: int) -> tuple:
cur=self.head
for i in range(self.cur_level-1,-1,-1):
while cur.forwards[i] and cur.forwards[i].value<target:
cur=cur.forwards[i]
if cur.forwards[i] and cur.forwards[i].value==target:
return (cur,i)
return (None,None)
def search(self, target: int) -> bool:
return True if self._search(target) else False
def add(self, num: int) -> None:
cur=self._search(num)
if cur:
cur.count+=1
else:
cur=self.head
level=self.random_level()
newnode=listNode(num,level)
if self.cur_level<level:
self.cur_level=level
for i in range(self.cur_level-1,-1,-1):
while cur.forwards[i] and cur.forwards[i].value<num:
cur=cur.forwards[i]
if i<level:
newnode.forwards[i]=cur.forwards[i]
cur.forwards[i]=newnode
def erase(self, num: int) -> bool:
cur,level=self._search_before(num)
if cur:
nxt=cur.forwards[level]
nxt.count-=1
if not nxt.count:
for i in range(nxt.get_level()-1,-1,-1):
while cur.forwards[i] and cur.forwards[i]!=nxt:
cur=cur.forwards[i]
if cur.forwards[i]:
cur.forwards[i]=cur.forwards[i].forwards[i]
return True
else:
return False