当前位置:网站首页>跳表的实现

跳表的实现

2022-07-28 15:00:00 今天也要写bug、

跳表的实现

一般跳表会设计一个最大层数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、哈希表再极端场景下哈希冲突高,效率下降厉害,需要红黑树补足接力。
原网站

版权声明
本文为[今天也要写bug、]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_52670477/article/details/125962269