跳跃表
一,基本原理
1,引入跳跃表
下图是一个简单的有序单链表。
众所周知,链表是一种插入和删除十分便捷,但是查找比较困难的数据结构,如果要查找一个节点,需要遍历此节点之前的所有节点,比如说,我要查找值为31的节点,那么我要遍历前面9个元素,然后才能找到31。
那么有没有一种方法可以使链表既可以快速的查找,却又不会失去插入的便利性呢?
有人提出增加层数来减少查询的次数,每层是上一层元素个数的1/2,比如说一层链表有10个元素,我第二层有5个元素,第三层有2个元素,那么先从第三层开始查找,然后是依次往下查找,这样的话,查找的次数就会减少,比较贴近于二分查找的效率。
如下图,生成多层后查询值为31的节点比单链表要快一些。
层数 | 查找次数 |
---|---|
1 | 9 |
2 | 6 |
3 | 5 |
4 | 5 |
上面介绍的特殊链表叫跳跃表,通过增加层数的方式是的查找效率趋近于二分查找,当然有得必有失,这种查找的便利性是通过牺牲一部分空间得到的,不过相比较节省的时间而言,牺牲的空间也是值得的。
可能上面这个例子节点数量较少,大家感受不深,看下图,当节点数量较大时,上层一个判断就能减少二十五万次的查询,这样的效率就会高很多。
例如我当前查询值为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。
2,跳跃表
跳跃表是一种通过增加多层结构,达到一种查询的平均效率趋近于二分查找的链表。
- 跳表是可以实现平均二分查找的有序链表;
- 每个元素插入时随机生成它的level;
- 最底层包含所有的元素;
- 如果一个元素出现在level(x),那么它肯定出现在x以下的level中;
- 每个索引节点包含两个指针,一个向前,一个向后;
- 跳表查询、插入、删除的平均时间复杂度为O(log n),与平衡二叉树接近;
上图就是一个随机产生的跳跃表,大家应该发现了,这个链表并不是严格按照每层元素个数是上一层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 查询
2.2 插入节点
2.3 删除节点
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); \
}
/**********************************************************************
* 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)))
#define SKIP_LIST_NODE_ADD(link, pos, end) \
{\
(link)->next = (end); \
(link)->prev = (pos); \
(end)->prev = (link); \
(pos)->next = (link); \
}
/**********************************************************************
* 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)
/**********************************************************************
* 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);
}