639-跳跃表SkipList

阳光穿透心脏的1/2处 2022-09-12 11:48 78阅读 0赞

跳跃表SkipList介绍

在这里插入图片描述
在这里插入图片描述
所有的元素都是在最底下(第一层)出现的,然后往上走,会把第一层的某些元素在第二层存储一下,然后再往上走,会把第二层的某些元素在第三层存储一下,以此类推下去。
每一层都是有序的链表,默认从小到大。

哪些元素往上面一层放呢?

在这里插入图片描述
K=1,然后进入while循环,随机数是0或者1,相当于是不断抛硬币的过程,符号我们正态分布的过程。也就是说,抛一万次硬币,得到的结果近似一半正面朝上,一半的正面朝下,符合正态分布的结果。
我们根据抛硬币的方式决定下面这一层的某一个元素是否需要在上一层再存储一遍。
也就是说,从我们的正态分布的角度来看,最终我们可能会把趋于下面这一层的一半的元素提升到上面这一层来。
再往上一层走,就把下面的第二层的所有元素进行抛硬币,然后把近似一半的元素提到第三层再存储一遍。
这样,最终就和我们的二叉树一样了,最顶层可能就1个元素节点。
然后它的下面一层可能是2个,以此类推。
其实,这个过程,搭建起来跳表的过程,元素的组织方式和我们的二叉搜索树是非常相似的。
在这里插入图片描述

跳跃表的查找搜索元素

在这里插入图片描述
可以想象:从根节点开始,因为越往上,元素越少。
因为117大于21,意味着它把21之前的元素就不用比较了,这和我们的二分搜索(二叉搜索树)很相似。
相当于就是从根节点,在每一层找一个合适的节点,然后再往下一层跑。
就是二叉搜索树的搜索方式!!!
在这里插入图片描述

跳跃表的插入

搜索元素是从最上面的那层元素开始搜索的。
如果要插入元素,

在这里插入图片描述
如果我们要插入119这个数字,通过抛硬币,决定119要插几行。
跳表本身是3行,插2行是没有问题的。
在最后一行按序插入,因为要插2行,所以在第二行也插了一个119
假如我们要插入119,但是抛硬币抛出了4层,但是跳表只有3层,但是最终我们也是要插4层的,也就是说,在最上层(第4层)就只有一个数字119
但是如果说,这个跳表只有3层,在插入数字119的时候,抛硬币的结果是5层,在5层里面都要添加119这个数字,但是跳表只有3层,所以我们最终也是只插4层就可以了。
在这里插入图片描述
跳表的层数增长是一层一层增长的,不会一下子多个层,每个层放的数据一样。
在这里插入图片描述
K初始化=1,因为一定要在最底层添加元素。然后按照随机进行K++

跳跃表的删除操作

在这里插入图片描述
如果我们要删除元素37,首先得查找,我们是从最上层开始查找,我们在最上层(level3)一下子就找到37这个元素了,既然这个37在level3出现了,那么37肯定还出现在level2和level1中,如果我们在第三层,也就是最上层找到37了,然后往下,把这3个节点都删除了。也就是:在每一层中,把37的前面1个节点的地址域写成37后面的那个节点的地址。
如果我们要删除元素71,首先是查找,在最上层中遍历到37,比37大,再往找的话,已经是末尾节点1了,所以71在level3上不存在,所以从37直接往下走,走到level2层,然后往后走,找到了71,然后就不用往后找了,往下找!因为没有到最底层,所以下面都是71,从上到下,把这个垂直上的71全部删除掉,也就是每一层的71的前一个节点的地址域存储71后一个节点的地址。
在这里插入图片描述

跳跃表的优缺点

在这里插入图片描述
在这里插入图片描述

插入元素119只改动这部分的数据:
在这里插入图片描述
如果在并发进行的时候,71前面的这些节点的改动在这里插入图片描述
和71后面的插入元素操作是可以并发执行的,互不影响的。
如果在多线程环境下,并发条件下,使用跳跃表进行线程安全控制的话,如果添加锁,插入119,就不需要给整个跳跃表进行加锁控制了,只需要给71后面这一段添加锁的控制,实现线程互斥就可以了。
但是,对于红黑树来说,红黑树节点着色,以及节点的旋转,它可能影响的是整个树都要改变,红黑树在实现并发安全的时候,插入和删除操作都必须给整个树进行锁的控制了。红黑树在进行并发操作,实现线程互斥,锁的粒度大,而跳跃表实现锁的粒度比较小,更利于并发的效率。

跳跃表在删除元素也是一样的。
在这里插入图片描述
如果我们要删除元素71,也是一样的,对下面这一部分进行加锁控制就可以了在这里插入图片描述
37之前的部分在进行修改的时候是可以并发进行的。

但是对于红黑树来说,如果删除的是一个黑色节点,需要从兄弟节点借一个黑色,如果兄弟节点借不了,把兄弟节点置为红色,然后向上回溯。极端情况就是回溯到根节点,把所有路径上都减少一个根节点,意味着红黑树的删除需要对整个树进行加锁解锁控制。

但是跳跃表相当于红黑树,是在空间换时间,占用内存比较大。红黑树中,所有元素值只是存储一个节点的,但是在跳跃表中,所有的元素不仅仅在最下面那层出现,而且根据抛硬币选出来的元素在上面层进行存储,以此类推上去,模拟了一个二分查找搜索树的过程,简化了红黑树,达到O(logn)的搜索和删除的时间复杂度,而且实现起来比较简单。
在实际应用场景中,跳跃表和红黑树在很多情况下是可以互换的。

redis:非常强大的分布式缓存服务器,源码的数据存储就是跳跃表,增删查是O(logn)。

跳跃表SkipList的代码实现

  1. #include<stdio.h>
  2. #include<malloc.h>
  3. #include<string.h>
  4. #include<assert.h>
  5. #include<iostream>//cin;
  6. using namespace std;
  7. #define MAX_LEVEL 10//最大层数
  8. typedef int KeyType;
  9. typedef int ElemType;
  10. typedef struct SkipNode
  11. {
  12. KeyType key;
  13. ElemType value;
  14. SkipNode *forward[];//方向指针
  15. }SkipNode;
  16. //跳跃表
  17. typedef struct
  18. {
  19. int level;//层数
  20. SkipNode *head;//头节点
  21. }SkipList;
  22. //创建节点
  23. SkipNode* BuyNode(int level,int key,int value)
  24. {
  25. SkipNode *s=(SkipNode *)malloc(sizeof(SkipNode)+level*sizeof(SkipNode*));
  26. if(NULL == s) exit(1);
  27. memset(s,0,sizeof(SkipNode)+level*sizeof(SkipNode*));
  28. s->key=key;
  29. s->value=value;
  30. return s;
  31. }
  32. void Freenode(SkipNode *p)//释放节点
  33. {
  34. free(p);
  35. }
  36. SkipList* CreateSkipList()//创建跳跃表
  37. {
  38. SkipList *s = (SkipList *)malloc(sizeof(SkipList));
  39. if(NULL == s) exit(1);
  40. memset(s,0,sizeof(SkipList));
  41. s->level=0;
  42. s->head = BuyNode(MAX_LEVEL-1,0,0);
  43. for(int i=0;i < MAX_LEVEL;i++)
  44. {
  45. s->head->forward[i]=NULL;
  46. }
  47. return s;
  48. }
  49. //随机产生层数 抛硬币
  50. int RandomLevel()
  51. {
  52. int k=1;
  53. //srand(time(NULL));
  54. while(rand()%2)
  55. {
  56. k++;
  57. }
  58. k=(k<MAX_LEVEL)? k: MAX_LEVEL;
  59. return k;
  60. }
  61. //插入节点
  62. bool InsertItem(SkipList *s,int key,int value)
  63. {
  64. SkipNode *update[MAX_LEVEL];
  65. SkipNode *p, *q = NULL;
  66. p = s->head;
  67. int k = s->level;
  68. //从最高层往下查找需要插入的位置
  69. //填充update
  70. for(int i=k-1; i >= 0; i--)
  71. {
  72. while((q=p->forward[i])&&(q->key<key))
  73. {
  74. p=q;
  75. }
  76. update[i]=p;
  77. }
  78. //不能插入相同的key
  79. if(q != NULL && q->key == key)
  80. {
  81. return false;
  82. }
  83. //产生一个随机层数K
  84. //新建一个待插入节点q
  85. //一层一层插入
  86. k=RandomLevel();
  87. //更新跳表的level
  88. if(k > (s->level))
  89. {
  90. for(int i=s->level; i < k; i++)
  91. {
  92. update[i] = s->head;
  93. }
  94. s->level=k;
  95. }
  96. q = BuyNode(k,key,value);
  97. //逐层更新节点的指针,和普通列表插入一样
  98. for(int i=0;i<k;i++)
  99. {
  100. q->forward[i]=update[i]->forward[i];
  101. update[i]->forward[i]=q;
  102. }
  103. return true;
  104. }
  105. //搜索指定key的value
  106. int SearchValue(SkipList *s,int key)
  107. {
  108. SkipNode *p,*q=NULL;
  109. p=s->head;
  110. //从最高层开始搜
  111. int k=s->level;
  112. for(int i=k-1; i >= 0; i--){
  113. while((q=p->forward[i])&&(q->key<=key))
  114. {
  115. if(q->key == key)
  116. {
  117. return q->value;
  118. }
  119. p=q;
  120. }
  121. }
  122. return NULL;
  123. }
  124. //删除指定的key
  125. bool RemoveValue(SkipList *s,int key)
  126. {
  127. SkipNode *update[MAX_LEVEL];
  128. SkipNode *p,*q=NULL;
  129. p=s->head;
  130. //从最高层开始搜
  131. int k=s->level;
  132. for(int i=k-1; i >= 0; i--)
  133. {
  134. while((q=p->forward[i])&&(q->key<key))
  135. {
  136. p=q;
  137. }
  138. update[i]=p;
  139. }
  140. if(q != NULL &&q->key==key)
  141. {
  142. //逐层删除,和普通列表删除一样
  143. for(int i=0; i<s->level; i++)
  144. {
  145. if(update[i]->forward[i]==q)
  146. {
  147. update[i]->forward[i]=q->forward[i];
  148. }
  149. }
  150. Freenode(q);
  151. //如果删除的是最大层的节点,那么需要重新维护跳表的
  152. for(int i=s->level - 1; i >= 0; i--)
  153. {
  154. if(s->head->forward[i]==NULL)
  155. {
  156. s->level--;
  157. }
  158. }
  159. return true;
  160. }
  161. else
  162. {
  163. return false;
  164. }
  165. }
  166. void PrintSkipList(SkipList *s)//打印
  167. {
  168. //从最高层开始打印
  169. SkipNode *p,*q=NULL;
  170. //从最高层开始搜
  171. int k = s->level;
  172. for(int i=k-1; i >= 0; i--)
  173. {
  174. p = s->head;
  175. while(q = p->forward[i])
  176. {
  177. printf("%d -> ",p->value);
  178. p = q;
  179. }
  180. printf("\n");
  181. }
  182. printf("\n");
  183. }
  184. int main()
  185. {
  186. SkipList *s=CreateSkipList();
  187. for(int i=1;i<=19;i++)
  188. {
  189. InsertItem(s,i,i*2);
  190. }
  191. PrintSkipList(s);
  192. //搜索
  193. int i=SearchValue(s,4);
  194. printf("i=%d\n",i);
  195. //删除
  196. bool b=RemoveValue(s,4);
  197. if(b)
  198. {
  199. printf("删除成功\n");
  200. }
  201. PrintSkipList(s);
  202. return 0;
  203. }

在这里插入图片描述

Java代码实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
范围查找非常方便,是有序的双向链表实现的,比红黑树效率高。

  1. package com.fixbug;
  2. import java.util.TreeSet;
  3. import java.util.concurrent.ConcurrentSkipListSet;
  4. /**
  5. * 跳跃表的代码实现 java.util.concurrent Java并发包 ConcurrentSkipListSet ConcurrentSkipListMap
  6. * @param <T>
  7. */
  8. class SkipList<T extends Comparable<T>>{
  9. //指向跳跃表最上面一层链表的头节点,相当于二分查找
  10. private HeadNode<T> head;
  11. /**
  12. * 跳跃表的初始化,初始只有一层链表
  13. */
  14. public SkipList() {
  15. this.head = new HeadNode<>(null, null, null, null, null, 1);
  16. }
  17. /**
  18. * 把key添加到跳跃表当中 [key, value]
  19. * @param key
  20. */
  21. public void put(T key){
  22. //1.在跳跃表中搜索key应该插入的位置
  23. Node<T> pre = this.head;//头节点
  24. Node<T> cur = pre.right;//最上层的首元素
  25. for(;;){
  26. if(cur != null){
  27. if(cur.data.compareTo(key) < 0){
  28. //当前节点比key小
  29. pre = cur;//向后推进
  30. cur = cur.right;
  31. continue;//继续判断当前链表的右边节点,找第一个比key大的节点
  32. } else if(cur.data.compareTo(key) == 0) {
  33. return;//不会重复插入key
  34. }
  35. }
  36. //已经是第一层链表了,那么key应该插入到pre节点的后面
  37. if(pre.down == null){
  38. break;
  39. }
  40. pre = pre.down;//pre走到下面一层链表
  41. cur = pre.right;//更新cur指向pre节点的下一个节点
  42. }
  43. //2.key应该插入到pre节点的后面。抛硬币决定key需要插入的层数k
  44. int level = getLevel();
  45. HeadNode<T> h = this.head;
  46. if(level > h.level){
  47. //key添加的层数太多了,最多只增加一层
  48. level = h.level + 1;
  49. //生成新的一层链表,链接到跳跃表当中
  50. h = new HeadNode<>(null, null, this.head, null, null, level);
  51. this.head.up = h;
  52. this.head = h;
  53. }
  54. //3.层数k <= 跳跃表的层数 : 直接插入 层数k > 跳跃表的层数 : 只增长一层
  55. Node<T>[] nodeArr = new Node[level];
  56. Node<T> index = null;
  57. //先链接up域
  58. for(int i=0; i < nodeArr.length; ++i){
  59. nodeArr[i] = index = new Node<>(key, index, null, null, null);
  60. }
  61. // 再链接down域
  62. for(int i=0; i < nodeArr.length-1; ++i){
  63. nodeArr[i].down = nodeArr[i+1];
  64. }
  65. // 插入key
  66. for(int i = nodeArr.length-1; i >= 0; --i){
  67. //下面四句代码,类似双向链表的插入操作
  68. nodeArr[i].right = pre.right;
  69. nodeArr[i].left = pre;
  70. pre.right = nodeArr[i];
  71. if(nodeArr[i].right != null){
  72. nodeArr[i].right.left = nodeArr[i];
  73. }
  74. if(0 == i){
  75. //所有层数的链表数据都已经添加完成
  76. break;
  77. }
  78. while(pre.up == null){
  79. //pre往当前链表的前边节点回退,找见第一个可以往上面链表走的节点
  80. pre = pre.left;
  81. }
  82. pre = pre.up;
  83. }
  84. }
  85. /**
  86. * 跳跃表的删除操作
  87. * @param key
  88. */
  89. public void remove(T key){
  90. //1. 找到待删除的节点,让cur指向
  91. Node<T> pre = this.head;
  92. Node<T> cur = pre.right;
  93. for(;;){
  94. if(cur != null){
  95. if(cur.data.compareTo(key) < 0){
  96. pre = cur;
  97. cur = cur.right;
  98. continue;
  99. } else if(cur.data.compareTo(key) == 0){
  100. break;
  101. }
  102. }
  103. if(pre.down == null){
  104. break;
  105. }
  106. pre = pre.down;
  107. cur = pre.right;
  108. }
  109. //2.删除cur指向的节点
  110. if(cur == null){
  111. return;
  112. }
  113. //3.开始删除cur指向的节点
  114. while (cur != null){
  115. cur.left.right = cur.right;
  116. if(cur.right != null){
  117. cur.right.left = cur.left;
  118. }
  119. cur = cur.down;
  120. }
  121. //4.删除完成后,从上往下检查,把空链表全部释放
  122. Node<T> h = this.head;
  123. while (h != null && h.down != null){
  124. if(h.right == null){
  125. // 表示当前链表是个空链表
  126. this.head = (HeadNode<T>) h.down;
  127. h = h.down;
  128. } else {
  129. break;
  130. }
  131. }
  132. }
  133. /**
  134. * 跳跃表的查询操作
  135. * @param key
  136. * @return
  137. */
  138. public boolean query(T key){
  139. Node<T> pre = this.head;
  140. Node<T> cur = pre.right;
  141. for(;;){
  142. if(cur != null){
  143. if(cur.data.compareTo(key) < 0){
  144. pre = cur;
  145. cur = cur.right;
  146. continue;
  147. } else if(cur.data.compareTo(key) == 0){
  148. return true;
  149. }
  150. }
  151. if(pre.down == null){
  152. break;
  153. }
  154. pre = pre.down;
  155. cur = pre.right;
  156. }
  157. return false;
  158. }
  159. /**
  160. * 打印每一层链表的内容
  161. */
  162. public void show(){
  163. System.out.println("跳跃表的层数:" + this.head.level);
  164. Node<T> h = this.head;
  165. while(h != null){
  166. Node<T> cur = h.right;
  167. while(cur != null){
  168. System.out.printf("%-5d", cur.data);
  169. cur = cur.right;
  170. }
  171. System.out.println();
  172. h = h.down;
  173. }
  174. }
  175. /**
  176. * 模拟抛硬币,决定key插入的层数
  177. * @return
  178. */
  179. private int getLevel() {
  180. int k = 1;
  181. while(Math.random() >= 0.5){
  182. k++;
  183. }
  184. return k;
  185. }
  186. /**
  187. * 跳跃表节点的类型
  188. * @param <T>
  189. */
  190. static class Node<T extends Comparable<T>>{
  191. T data;//任何类型都有可能
  192. Node<T> up;//4个方向
  193. Node<T> down;
  194. Node<T> left;
  195. Node<T> right;
  196. public Node(T data, Node<T> up, Node<T> down, Node<T> left, Node<T> right) {
  197. this.data = data;
  198. this.up = up;
  199. this.down = down;
  200. this.left = left;
  201. this.right = right;
  202. }
  203. }
  204. /**
  205. * 每一层链表,头节点的类型,用来额外记录层数
  206. * @param <T>
  207. */
  208. static class HeadNode<T extends Comparable<T>> extends Node<T>{
  209. int level;//跳跃表每一层链表的头节点记录当前链表所处的层数
  210. public HeadNode(T data, Node<T> up, Node<T> down, Node<T> left, Node<T> right, int level) {
  211. super(data, up, down, left, right);
  212. this.level = level;
  213. }
  214. }
  215. }
  216. /**
  217. * 跳跃表代码测试
  218. */
  219. public class SkipListTest
  220. {
  221. public static void main( String[] args ) {
  222. long begin, end;
  223. // begin = System.currentTimeMillis();
  224. SkipList<Integer> list = new SkipList<>();
  225. for (int i = 0; i < 10; i++) {
  226. list.put((int)(Math.random() * 100));
  227. }
  228. list.put(18);
  229. list.show();
  230. System.out.println(list.query(18));
  231. System.out.println();
  232. list.remove(18);
  233. list.show();
  234. System.out.println(list.query(18));
  235. // end = System.currentTimeMillis();
  236. // System.out.println("skiplist time:" + (end-begin) + "ms");
  237. // begin = System.currentTimeMillis();
  238. // ConcurrentSkipListSet<Integer> set = new ConcurrentSkipListSet<>();
  239. // for (int i = 0; i < 20000; i++) {
  240. // set.add((int)(Math.random() * 100));
  241. // }
  242. // end = System.currentTimeMillis();
  243. // System.out.println("ConcurrentSkipListSet time:" + (end-begin) + "ms");
  244. }
  245. }

发表评论

表情:
评论列表 (有 0 条评论,78人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Skiplist

    跳跃表(skiplist)是一种随机化的数据结构,是一种可以与平衡树媲美的层次化链表结构——查找、删除、添加等时间复杂度都是O(log n),许多知名的开源软件中的数据结构均采

    相关 SkipList

    > 1.聊一聊跳表作者的其人其事 > > 2. 言归正传,跳表简介 > > 3. 跳表数据存储模型 > > 4. 跳表的代码实现分析 > > 5. 论文,代码下载及参考