跳跃表学习

跳跃表

skiplist code
参考博客

一,基本原理

1,引入跳跃表

下图是一个简单的有序单链表。

simplelist
众所周知,链表是一种插入和删除十分便捷,但是查找比较困难的数据结构,如果要查找一个节点,需要遍历此节点之前的所有节点,比如说,我要查找值为31的节点,那么我要遍历前面9个元素,然后才能找到31。

那么有没有一种方法可以使链表既可以快速的查找,却又不会失去插入的便利性呢?

有人提出增加层数来减少查询的次数,每层是上一层元素个数的1/2,比如说一层链表有10个元素,我第二层有5个元素,第三层有2个元素,那么先从第三层开始查找,然后是依次往下查找,这样的话,查找的次数就会减少,比较贴近于二分查找的效率。

如下图,生成多层后查询值为31的节点比单链表要快一些。

层数查找次数
19
26
35
45

skip_list_introduction
上面介绍的特殊链表叫跳跃表,通过增加层数的方式是的查找效率趋近于二分查找,当然有得必有失,这种查找的便利性是通过牺牲一部分空间得到的,不过相比较节省的时间而言,牺牲的空间也是值得的。

可能上面这个例子节点数量较少,大家感受不深,看下图,当节点数量较大时,上层一个判断就能减少二十五万次的查询,这样的效率就会高很多。

例如我当前查询值为262145的节点,如果是一层的话,查询次数为262145次,但是如果是有N层(如果按照完全的每层元素个数是上一层1/2来算,N为19),查找次数为N+2。

链表第一层有n个元素,则第二层有n/2 个元素、第二层有 n/4 个元素、N级索引就有 n/2N-1个元素。最高级索引一般有2个元素,即:最高级索引N满足 2 = n/2N-1,即 N = log2n。

skip_list_huge

2,跳跃表

跳跃表是一种通过增加多层结构,达到一种查询的平均效率趋近于二分查找的链表。

  1. 跳表是可以实现平均二分查找的有序链表;
  2. 每个元素插入时随机生成它的level;
  3. 最底层包含所有的元素;
  4. 如果一个元素出现在level(x),那么它肯定出现在x以下的level中;
  5. 每个索引节点包含两个指针,一个向前,一个向后;
  6. 跳表查询、插入、删除的平均时间复杂度为O(log n),与平衡二叉树接近;

skip_list

上图就是一个随机产生的跳跃表,大家应该发现了,这个链表并不是严格按照每层元素个数是上一层1/2产生的。

一般来说,很少有人严格按照每层元素个数是上一层的1/2,这样的算法进行跳跃表的插入,因为会造成每次插入和删除时,都需要调整整个链表的层数,造成整个算法的复杂度上升。

比较通用的方式是通过抛硬币的方式决定插入的节点的层数。

假设我要在上面这个链表中插入一个节点,节点值为18,那么我们需要先通过抛硬币的方式决定18这个节点所拥有的层数,第一层是包含所有节点的,所以18默认层数是1,然后第一次抛硬币,结果是正面,所以18同样在第二层,然后第二次抛硬币,结果是反面,所以最终结果为18在第一层和第二层。

然后找到插入18的位置,即插入到第二层的14 - 20之间和第一层的17 - 20之间,完成整个插入操作。

为什么通过抛硬币的方式呢?因为硬币的正面和反面的概率都是1/2,连续两次正面的几率为1/2 * 1/2 = 1/4,…以此类推,最终平均概率来算,还是趋近于每层元素个数是上层的1/2。

对于删除的操作就更简单了,直接找到每层的节点,依次删除即可。

这样的话,整个跳跃表的插入和删除都不需要太多的操作。

2.1 查询

skip_list_search

2.2 插入节点

skip_list_add

2.3 删除节点

skip_list_delete

3,使用场景

3.1 Redis

Redis是一个使用ANSI C编写的开源、支持网络、基于内存、分布式、可选持久性的键值对存储数据库。

3.2 HBase MemStore

HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。

MemStore是HBase非常重要的组成部分。

3.3 LevelDB

LevelDB是一个可持久化的KV数据库引擎,由Google传奇工程师Jeff Dean和Sanjay Ghemawat开发并开源。

3.4 RocksDB

RocksDB是用于键值数据的高性能嵌入式数据库。

二,实现代码

1,数据结构

/***** Struct Definitions *****/
typedef struct skiplink {
        struct skiplink *prev;/* Points at the next previous in the list */
        struct skiplink *next;/* Points at the next node in the list */
}tSkipLink;

typedef struct skiplist {
        int level;/* Max level in list */
        int count;/* Number of Nodes in list */
        struct skiplink head[SKIPLIST_MAX_LEVEL];/* Header List Node */
}tSkipList;

typedef struct skipnode {
        int key;
        int value;
        struct skiplink link[0];
}tSkipNode;

/***** Struct Definitions End *****/

/***  MACRO Definition  ***/

/*The purpose of this macro is to determine the stratification probability*/
#define SKIPLIST_STRATIFICATION_PROBABILITY    0.25
#define SKIPLIST_MAX_LEVEL 32  /* Should be enough for 2^32 elements */

/***  MACRO Definition End ***/

2,创建链表

#define SKIP_LIST_LINK_INIT(link) \
    {\
        (link)->prev = (link); \
        (link)->next = (link); \
    }

skip_list_code_init

/**********************************************************************
 * Create a skiplist, the function is to record the head node of each
 * layer of the entire skiplist.
 **********************************************************************/
tSkipList *skipListCreate(void)
{
    int level = 0;;
    tSkipList *list = NULL;

    list = (tSkipList *)malloc(sizeof(*list));
    if (list != NULL)
    {
        list->level = 1;
        list->count = 0;

        for (level = 0; level < (sizeof(list->head)/sizeof(list->head[0])); level++)
        {
            SKIP_LIST_LINK_INIT(&list->head[level]);
        }
    }

    return list;
}

3,增加节点

#define SKIP_LIST_NODE_GET(ptr, type, member) \
        ((type *)((char *)(ptr) - (size_t)(&((type *)0)->member)))

skip_list_code_node_get

#define SKIP_LIST_NODE_ADD(link, pos, end) \
    {\
        (link)->next = (end); \
        (link)->prev = (pos); \
        (end)->prev = (link); \
        (pos)->next = (link); \
    }

skip_list_code_node_add

/**********************************************************************
 * According to the defined stratification probability, the number of
 * layers for each node is generated.
 * Algorithm idea:
 * By default, they belong to the first layer, the probability of belonging
 * to the second layer is p, the probability of belonging to the third layer
 * is p*p, and the probability of belonging to the fourth layer is p*p*p ...
 **********************************************************************/
int skipListRandomLevel(void)
{
    int level = 1;
    double p = SKIPLIST_STRATIFICATION_PROBABILITY;

    while ((random() & 0xffff) < 0xffff * p)
    {
        level++;
    }

    return level > SKIPLIST_MAX_LEVEL ? SKIPLIST_MAX_LEVEL : level;
}

/**********************************************************************
 * Create a node whose value is value and index is key.
 **********************************************************************/
tSkipNode *skipListNodeCreate(int level, int key, int value)
{
    tSkipNode *node = NULL;

    node = (tSkipNode *)malloc(sizeof(*node) + level * sizeof(tSkipLink));
    if (node != NULL)
    {
        node->key = key;
        node->value = value;
    }
    return node;
}

/**********************************************************************
 * Insert a node into the skip list.
 **********************************************************************/
tSkipNode *skipListInsert(tSkipList *list, int key, int value)
{
    /*generate a number of layers for the nodes that need to be inserted*/
    int insertNodeLevel = skipListRandomLevel();
    int level = 0;

    /*if the number of layers generated is larger than the highest layer 
     *of the current skiplist, update the number of layers of the skiplist.*/
    if (insertNodeLevel > list->level)
    {
        list->level = insertNodeLevel;
    }

    tSkipNode *node = skipListNodeCreate(insertNodeLevel, key, value);
    if (node != NULL)
    {
        level = list->level - 1;
        /*get the top-level head node.*/
        tSkipLink *pos = &(list->head[level]);
        tSkipLink *end = &(list->head[level]);

        for (; level >= 0; level--)
        {
            pos = pos->next;
            while (pos != end)
            {
                /*Get the current node based on the offset*/
                tSkipNode *nd = SKIP_LIST_NODE_GET(pos, tSkipNode, link[level]);
                if (nd->key >= key)
                {
                    end = &(nd->link[level]);
                    break;
                }

                pos = pos->next;
            }
            pos = end->prev;
            if (level < insertNodeLevel)
            {
                SKIP_LIST_NODE_ADD(&node->link[level], pos, end);
            }
            pos--;
            end--;
        }

        list->count++;
    }

    return node;
}

4,删除节点

#define SKIP_LIST_DYN_SCAN(pos, n, end) \
        for (n = pos->next; pos != end; pos = n, n = pos->next)

#define SKIP_LIST_LINK_DELETE(link) \
    {\
        (link)->prev->next = (link)->next; \
        (link)->next->prev = (link)->prev; \
        (link)->prev = (link); \
        (link)->next = (link); \
    }

#define SKIP_LIST_IS_EMPTY(link)  (link)->next == (link)

skip_list_code_node_delete

/**********************************************************************
 * Delete the corresponding of node from the skiplist.
 **********************************************************************/
void skipListNodeRemove(tSkipList *list, tSkipNode *node, int level)
{
    int currentLevel = 0;

    for (currentLevel = 0; currentLevel < level; currentLevel++)
    {
        SKIP_LIST_LINK_DELETE(&(node->link[currentLevel]));
        if (SKIP_LIST_IS_EMPTY(&(list->head[currentLevel])))
        {
            list->level--;
        }
    }
    free(node);
    list->count--;
}

/**********************************************************************
 * Search and delete the corresponding of node from the skiplist.
 **********************************************************************/
void skipListNodeDelete(tSkipList *list, int key)
{
    int level = list->level - 1;
    tSkipNode *node = NULL;
    tSkipLink *n = NULL;
    tSkipLink *pos = &(list->head[level]);
    tSkipLink *end = &(list->head[level]);

    for (; level >= 0; level--)
    {
        pos = pos->next;
        SKIP_LIST_DYN_SCAN(pos, n, end)
        {
            /*Get the current node based on the offset*/
            node = SKIP_LIST_NODE_GET(pos, tSkipNode, link[level]);
            if (node->key > key)
            {
                end = &(node->link[level]);
                break;
            }
            else if (node->key == key)
            {
                skipListNodeRemove(list, node, level + 1);
            }
        }

        pos = end->prev;
        pos--;
        end--;
    }
}

5,查询节点

/**********************************************************************
 * Search the corresponding node in skiplist.
 **********************************************************************/
tSkipNode *skipListSearch(tSkipList *list, int key)
{
    int level = list->level - 1;
    tSkipNode *node = NULL;
    tSkipLink *pos = &(list->head[level]);
    tSkipLink *end = &(list->head[level]);

    for (; level >= 0; level--)
    {
        pos = pos->next;
        while (pos != end)
        {
            /*Get the current node based on the offset*/
            node = SKIP_LIST_NODE_GET(pos, tSkipNode, link[level]);
            if (node->key >= key)
            {
                end = &node->link[level];
                break;
            }

            pos = pos->next;
        }

        if (node->key == key)
        {
            return node;
        }

        pos = end->prev;
        pos--;
        end--;
    }

    return NULL;
}

6,删除链表

/**********************************************************************
 * Delete the entire skiplist.
 **********************************************************************/
void skipListDelete(tSkipList *list)
{
    tSkipLink *n = NULL;
    tSkipLink *pos = list->head[0].next;

    SKIP_LIST_DYN_SCAN(pos, n, &(list->head[0]))
    {
        /*Get the current node based on the offset*/
        tSkipNode *node = SKIP_LIST_NODE_GET(pos, tSkipNode, link[0]);
        free(node);
    }

    free(list);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值