跳表原理及实现(C++/Python)

参考文章

知乎—跳表这种高效的数据结构,值得每一个程序员掌握—somenzz

跳表是什么?

顾名思义,跳表即跳跃的链表。其添加元素、删除元素、查找元素的复杂度都为 O ( l g n ) O(lgn) O(lgn)

核心思想

类似倍增思想(其实是倍减?orz),层层向上构建索引,再具体一点就是,假设一级索引就是普通链表,二级索引就每两个元素取一个构建链表,三级索引就是每四个元素取一个构建链表……也就是说第 i i i级索引的元素个数是第 i − 1 i-1 i1级索引的元素个数的一半。看图会更好理解一点:

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(mlgn)。而我们从跳表的构造方式不难发现 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/2i1即可(不太严谨,别太在意)。

//得到一个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已经存在,那么递增其计数即可,否则要在跳表中新增一个节点,这种情况就比较麻烦了,我们要按照以下步骤进行:

  1. 构建新增节点,并决定它所位于的索引 i i i。这里多说一句,若一个元素位于第 i i i级索引,则其一定位于 < i <i <i的所有索引。
  2. 对于所有 < = 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

猜你喜欢

转载自blog.csdn.net/xiji333/article/details/114882305