Brief introduction and C++ implementation of Skiplist

0X 00 Foreword

A few days ago when looking at the Redis, see the Internet, he said, Redis is used Skiplistrather than a red-black tree, but when I gave the whole senseless.

What is called Skiplist, when I look it up, the translation is pretty straightforward-skip list. Looks very advanced, so swish and check the information immediately

The original intention of 0X 10 Skiplist

Before seeing the true face of Skiplist Lushan, let’s first understand how it appeared, or why it appeared.

Linked lists must be familiar to everyone. Generally speaking, it looks like this: The
Insert picture description here
above is an ordered linked list. We know that one advantage of linked lists is that they are convenient for deletion and insertion.

However, the fly in the ointment is that query is one of its weaknesses.

Because we search in the linked list, we can only search from the beginning one by one, so in general, the search efficiency of the linked list is O(n)

So based on this, there are great gods who start to plan. How to combine the convenience of linked lists with the search advantages of arrays ( referring to binary search under ordered arrays )?

So, so, the magical jump table came out (the great god is worthy of the great god, come out in minutes:))

0X 20 The true face of Skiplist

The above idea is good, but is it difficult to realize it?

I can only say that only we can't think of it, no god can't do it

Jump table jump table, the most important is in one word above, then rely on to jump it - , 索引it means指针

Well, it's also the exchange of space for efficiency (otherwise, the great god is not a true god, but can be transformed out of thin air)

Alright, the preparation is almost complete, and it’s time to "come out after a thousand calls"

Pictured above!
Insert picture description here
(Does it look familiar? Yes, maybe your data structure teacher talked about it in class!)

It looks weird, but people search for it but it’s a good idea


Let's take an example. Suppose we need to search 25. If it is a general ordered linked list, how many times do we need to compare? Didn’t do it, 9 times, all the ones before 25 have to be traversed again

So how do you use the skip table, how to search, and how many times do you need?

Knock on the blackboard, the point is coming!

Come we start

  • 1. currPoint the current pointer to the head node, and then check the node pointed to by the top pointer- 21, the first comparison, 21 is smaller than 25, so the current curr points to 21this node
  • 2. Repeat the same technique, and look at 21the first top pointer again, pointing to NULL, which means it's over, no, the second comparison
  • 3. At this time, move down one level, still point to NULL, move again, and compare for the third time
  • 4. The second layer (counting from bottom to top) points to 26this node. The fourth comparison, the ratio is 25greater, indicating that you still have to go down
  • 5. When it comes to the last layer, it is right now, or it is not, so the fifth comparison is carried out, and 25 is found

How about it, from the first 9 times to 5 times, it’s not half of the time.

0X 30 Skiplist's true face of Lushan

As we have seen just now, it is still very useful to skip the watch. However, in fact, the jump watch we saw just now is not a real jump watch. Why?

If you observe carefully, you will find that the above jump table has strict regulations: the number of nodes in the upper layer is 1/2 of the number in the lower layer

This is still very problematic. For example, when you insert a node, the structure is not destroyed? Or it must be changed in a certain way to maintain such a structure. This is almost the same as the red-black tree

What is the solution of the Great God? That is not to "regulate". Of course, what we are all about is to avoid the strict regulations above, and the rules become a kind of random

1:2 is not mandatory for the upper and lower layers. Whether a node is indexed or not, and how many layers of indexes are built, are all determined by a coin flip when the node is inserted

Of course, although the number of indexed nodes and indexed levels are random, in order to ensure the efficiency of the search, it is necessary to roughly ensure that the number of nodes at each level is equivalent to the structure of the previous section, so the real jump table that appears randomly is like this:
Insert picture description here
I found that the ratio is no longer strictly 1:2, but overall it is quite regular

The following is the process of inserting node 17. Insert picture description here
Performance analysis will not be done (because I don’t know 2333), but what needs to be remembered is that the insertion/deletion/lookup of the jump table is all O(logn)

0X 30 Hardcore coding-C++ implementation of Skiplist

PS: 99.99% of the following code is copy (of course, it is not a direct copy, it is a little bit of your own to the code code, and then add some comments), but I hope everyone can study hard and type it yourself to digest
the process of possible typing There is something wrong, so please pay attention! (There will be a link to the code source later)

skiplist.h

#ifndef _SKIPLSIT_H
#define _SKIPLSIT_H

#include <iostream>
#include <sstream>

template <typename K,typename V,int MAXLEVEL>

class skiplist_node	// 跳表的节点类 
{
    
    
private:
    K key;
    V value;
    skiplist_node<K,V,MAXLEVEL>* forwards;
public:
    skiplist_node()
    {
    
    
        forwards=new skiplist_node<K,V,MAXLEVEL> [MAXLEVEL];
        for(int i=0;i<MAXLEVEL;i++)
            forwards[i]=nullptr;
    }

    skiplist_node(K searchKey):key(searchKey)
    {
    
    
        for(int i=0;i<MAXLEVEL;i++)
            forwards[i]=nullptr;
    }

    ~skiplist_node()
    {
    
    
        delete [] forwards;
    }
};


template<typename K,typename V,MAXLEVEL=16>
class skiplist	//跳表类
{
    
         
    
    protected:
        K m_minKey;	//最小键值
        K m_maxKey;	//最大键值
        int man_curr_level;	//当前最大层数
        skiplist_node<K,V,MAXLEVEL>* m_pHeader;	//头指针
        skiplist_node<K,V,MAXLEVEL>* m_pTail;	//尾指针
        double uniformRandom();	//随机函数
        int randomLevel();	//生成随机层数

    public:
    	//别名
        typedef K KeyType;
        typedef V ValueType;
        typedef skiplist_node<K,V,MAXLEVEL> NodeType;

        const int max_level;	//最大层数

        skiplist(K minKey,K maxKey);

        virtual ~skiplist_node();

        void insert(K searchKey,V newValue); //插入

        void erase(K searchKey);	//删除

        const NodeType* find(K searchKey);	//查找

        bool empty() const;	//判断是否为空

        std::string printList();	//打印
};

skiplist.cc

#include "stdlib.h"

double skiplist::uniformRandom()	//随机函数
{
    
    
    return rand()/double(RAND_MAX);
}

int skiplist::randomLevel()	//生成随机层数
{
    
    
    int level=1;
    double p=0.5;
    while(uniformRandom()<p && level < MAXLEVEL)
                level++;
    return level;
}

skiplist::skiplist(K minKey,K maxKey):m_pHeader(NULL),m_pTail(NULL),	//构造函数
                                max_curr_level(1),max_level(MAXLEVEL),
                                m_minKey(minKey),m_maxKey(maxKey)
{
    
    
        m_pHeader = new NdoeType(m_minKey);
        m_pTail = new NodeType(m_maxKey);
        for(int i=0;i<MAXLEVEL;i++)
            m_pHeader->forwards[i]=m_pTail;
}

skiplist::~sikplist()	//析构函数
{
    
    
        NodeType* currNode =m_pHeader->forwards[0];
        while(currNode != m_pTail)
        {
    
    
            NodeType* tempNode = currNode;
            currNode = currNode->forwards[0];
            delete tempNode;      
         }
         delete m_pHeader;
         delete m_pTail;
}

void skiplist::insert(K searchKey,V newValue)	//插入函数
{
    
    
        skiplist_node<K,V,MAXLEVEL>* update[MAXKLEVEL];
        NodeType* currNode= m_pHear;
        for(int level=max_curr_level-1; level>=0; level--)  //这个循环用来查找待插入的位置
        {
    
    	//若第level指向的小于 待查找(searchKey) 的值,则在本层一直往下走
                while (currNdoe->forwards[level]->key<searchKey)	
                        currNode = currNode->forwards[level];
                //否则,到下一层
                update[level] = currNode;	//存储走过的路径
        }
        currNode = currNode->forwards[0];	//此时便是待插入的位置
        if(currNode->key == searchKey)	//若键值 key 相同
                    currNode->value = newValue;	//更新值
        else	//否则进行插入
        {
    
    
            int newlevel = randmoLevel();	//或者随机层数
            if(newlevel > max_curr_level)	//大于当前最大层数,则需要更新
            {
    
    
                for(int level = max_curr_level; level < newlevel; level++)
                    upadte[level]=m_pHeader;
                
                max_curr_level=newlevel;
            }
            currNode=new NodeType(searchKey,newValue);	
            //更新指针的指向,比新节点高的则指向updata后面的,比新节点低的则将updata指向新节点
            for(int lv=0;lv<max_curr_level;lv++)	
            {
    
    
                    currNode->forwards[lv]=update[lv]->forwards[lv];
                    update[lv]->forwards[lv]=currNode;
            }
        }   
}

void listskip::erase(K searchKey)
{
    
    
        skiplist_node<K,V,MAXLEVEL>* update[MAXLEVEL];
        NodeType* currNode = mpHeader;
        for(int level=max_curr_level-1; level>=0; level--)	//同上面的 insert
        {
    
    
                while(currNode->forwards[level]->key < searchKey)
                        currNode = currNode->forwards[level];
                
                update[level]=currNode;
        }
        currNode = currNode->forwards[0];	//找到需要查找的位置
        if(currNode->key == searchKey)//如果相等则说明存在需要删除
        {
    
    
            for(int lv=0;lv<max_curr_level;lv++)	//更新指针指向
            {
    
    
                    if(update[lv]->forwards[lv]!=currNode)  break;
                    update[lv]->forwards[lv]=currNode->forwards[lv];
            }
            delete currNode;    

                // update the max level
            while(max_curr_level > 0  && m_pHeader->forwards[max_curr_level]==nullptr)
                max_curr_level--;
        }
        
        
}

const NodeType* skiplist::find(K searchKey)
{
    
    
        NodeType* currNode = m_pHeader;
        for(int level=max_curr_level-1;level>=0;level--)	//同上,但寻找无需存储走过的路径
                while(currNode->forwards[level]->key < searchKey)
                            currNode = currNode->forwards[level];
        currNode = currNode->forwards[0];
        if(currNode->key==searchKey)	//找到返回 找到的节点
                return currNode;
        else return nullptr;	//否则返回空
}

bool skiplist::empty() const	//判断是否为空
{
    
    
        return (m_pHeader->forwards[0]==m_pTail);
}

std::string skiplist::printList()	//打印
{
    
    
        std::stringstream sstr;
        NodeTpye* currNode = m_pHeader->forwards[0];
        while(currNode != m_pTail)
        {
    
    
            sstr << "(" << currNode->key << "," << currNode->value << ")" << endl;
            currNode = currNode->forwards[0];
        }
        return sstr.str();
}

Because the code is a bit different from the above illustration, there are a few points to explain:
1. The code stores the form of <key, value>, so it is not just the above single value. key is used to compare the
head 2, the code, the tail node is stored in a specific min_valueand max_value, instead of the illustrated NULL, and therefore requires a specific key range

0X 30 Afterword

What needs to be clarified is that, except for a bunch of poems, most of the content above is based on online bigwigs. I hope you don't mind.

Finally, I post links to the articles of the big guys, let’s learn together~

Then we'll see you again

Jump table ─ ─ have never heard of but very sharp data structure

C++ Implementation of Skiplist

Guess you like

Origin blog.csdn.net/rjszz1314/article/details/104843524
Recommended