跳表的实现

跳表的实现

一般跳表会设计一个最大层数maxLevel(在Redis中为32)的限制,其次会设置一个多增加一层的概率p(在Redis中为0.25)。
产生越高的节点层数,概率越低。

  • 节点层数至少为1。而大于1的节点层数,满足一个概率分布。
  • 节点层数恰好等于1的概率为1-p。
  • 节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。
  • 节点层数大于等于3的概率为p2,而节点层数恰好等于3的概率为p2 *(1-p)。
  • 节点层数大于等于4的概率为p3,而节点层数恰好等于4的概率为p3*(1-p)。

当p=1/2时,每个节点所包含的平均指针数目为2。当p=1/4时,每个节点所包含的平均指针数目为1.33。

1206. 设计跳表

struct SkipNode
{
    //跳表的一个节点
    SkipNode(int val,int level)
    {
        _val=val;
        _next.resize(level,nullptr);
    }
    //当前节点的值
    int _val;
    //当前节点指向下一个节点的指针数组
    vector<SkipNode*>_next;
};

class Skiplist {
private:
    typedef struct SkipNode Node;
    //带一个头结点
    Node* _head;
    //最大高度为32
    size_t _max_level=32;
    double _p=0.25;
public:
    Skiplist() {
        //头结点一开始为1层
        _head=new Node(-1,1);
        srand(time(0));
    }
    bool search(int target) {
        Node*cur=_head;
        //当前的层数,用下标表示
        int level=_head->_next.size()-1;
        //如果查找到小于0的下标,说明没有找到
        while(level>=0)
        {
            //target和cur->next[level]->_val比较
            //比它小向下走,比它大向右走
            //当前节点下一个位置是空时也往下走
            if((cur->_next[level]&&cur->_next[level]->_val>target)||cur->_next[level]==nullptr)
            {
                level--;
            }
            else if(cur->_next[level]->_val<target)
            {
                cur=cur->_next[level];
            }
            else//相等,找到了
            {
                return true;
            }
        }
        return false;
    }
    //找寻num之前的节点的指针
    vector<Node*> FindPreNode(int num)
    {
        //保存插入位置每一层,前一个节点的指针
        vector<Node*>prev(_max_level,_head);
        Node*cur=_head;
        //然后找插入的位置
        int level=_head->_next.size()-1;
        while(level>=0)
        {
            //如果比下一个位置大或者等于,往下走
            //我们将该值插到相等值的最前面
            if((cur->_next[level]&&cur->_next[level]->_val>=num)||cur->_next[level]==nullptr)
            {
                //往下走的时候,记录这些值,方便插入的时候使用
                prev[level]=cur;
                level--;
            }
            //比下一个位置的值小于,往右走
            else if(cur->_next[level]&&cur->_next[level]->_val<num)
            {
                cur=cur->_next[level];
            }
        }
        return prev;
    }
    void add(int num) {
        //记录应该插入的位置
        vector<Node*> prev=FindPreNode(num);
        //获取随机的层数
        int n=RandomLevel();
        Node*newnode=new Node(num,n);
        //判断获取的层数和当前头节点的层数
        //当前头结点的层数有可能比新获取的层数小
        if(_head->_next.size()<n)
        {
            _head->_next.resize(n,nullptr);
        }
        //链接prev和newnode
        for(int i=0;i<n;i++)
        {
            newnode->_next[i]=prev[i]->_next[i];
            prev[i]->_next[i]=newnode;
        }
    }
    int RandomLevel()
    {
        int level=1;
        //RAND_MAX是rand()产生的最大值,乘上0.25,可以保证有0.25的几率进入while循环
        while(rand()<=RAND_MAX*_p&&level<_max_level)
        {
            ++level;
        }
        return level;
    }
    bool erase(int num) {
        //找到每一层的前一个
        vector<Node*> prev=FindPreNode(num);
        //第一层不存在,或者第一层下一个位置比num大,比如1(prev) 5(prev[0]->_next)这种情况
        //说明num不在表中,比如num=3
        if(prev[0]->_next[0]==nullptr||prev[0]->_next[0]->_val!=num)
        {
            //没找到这个值
            return false;
        }
        else
        {
            //prev[0]->_next[0]=num,此时需要删掉这个节点
            Node* del=prev[0]->_next[0];
            //del节点每一层的前后指针链接起来
            for(int i=0;i<del->_next.size();++i)
            {
                prev[i]->_next[i]=del->_next[i];
            }
            delete del;

            //删完以后,头结点可能需要降低高度
            //比如删除的节点高度是10,删完之后,整个表最高的节点只有5,但是头结点仍然为10
            int i=_head->_next.size()-1;
            while(i>=0)
            {
                //从上往下找到第一个非空的节点,然后删掉它
                if(_head->_next[i]==nullptr)
                {
                    i--;
                }
                else
                {
                    break;
                }
            }
            _head->_next.resize(i+1);
        }
        return true;
    }
};

skiplist跟平衡搜索树和哈希表的对比

  1. skiplist相比平衡搜索树(AVL树和红黑树)对比,都可以做到遍历数据有序,时间复杂度也差不多。skiplist的优势是:a、skiplist实现简单,容易控制。平衡树增删查改遍历都更复杂。 b、skiplist的额外空间消耗更低。平衡树节点存储每个值有三叉链,平衡因子/颜色等消耗。skiplist中p=1/2时,每个节点所包含的平均指针数目为2;skiplist中p=1/4时,每个节点所包含的平均指针数目为1.33;
  2. skiplist相比哈希表而言,就没有那么大的优势了。相比而言a、哈希表平均时间复杂度是O(1),比skiplist快。b、哈希表空间消耗略多一点。skiplist优势如下:a、遍历数据有序 b、skiplist空间消耗略小一点,哈希表存在链接指针和表空间消耗。c、哈希表扩容有性能损耗。d、哈希表再极端场景下哈希冲突高,效率下降厉害,需要红黑树补足接力。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
跳表是一种数据结构,可以高效地实现有序链表和有序集合,可以用来实现排行榜。跳表是通过在原始链表中添加多级索引节点来加速查找操作的。 跳表实现思路如下: 1. 创建一个带有头节点的链表,头节点的值为负无穷大,尾节点的值为正无穷大。 2. 在原始链表中,插入新的节点时,根据节点的值,决定是否在当前层级上添加索引节点。添加索引节点的概率可以根据需求进行调整。 3. 使用索引节点可以将跳表分为多个层级(level),每一层级都是一个有序链表。 4. 查询操作时,从最高层级开始,从左向右逐层搜索,直到找到目标值所在的区间(比目标值大的最小节点和比目标值小的最大节点之间)。 5. 对于插入和删除操作,首先在最底层进行,然后根据概率决定是否在上层级插入或删除对应的节点。 使用跳表实现排行榜的步骤如下: 1. 创建一个跳表,每个节点存储着用户的信息,包括用户的排名、分数等。 2. 初始化排行榜时,将所有用户按照分数从大到小顺序插入跳表中。 3. 当有新的用户加入或者用户的分数发生变化时,根据新的分数更新用户节点的位置。 4. 当需要查询某个用户的排名时,可以通过跳表中的索引节点,快速定位到该用户所在的层级,然后在该层级中按照顺序遍历找到目标节点,并返回排名。 通过以上步骤,我们可以使用跳表高效地实现排行榜功能。跳表的插入、删除和查找操作的时间复杂度都可以达到O(log n),在大数据量下具有较高的效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值