跳跃表是一种特殊的有序链表。通过空间换时间的方式解决链表查询效率低下的问题。
跳跃表是由多层有序链表组合而成的,最底一层的保存所有的数据,每向上的一层链表以此保存了下一层链表的部分数据,相邻的两层链表中元素相同的节点之间存在引用关系,一般上层的节点存在一个指向下层节点的引用。总的来说跳表=链表+多级索引。

图片.png

节点

查找

查找操作是自顶向下,从左到右,即从最上一层链表中从左到右查找,然后以此向下层查找,指导最下一层的某个节点结束
1 先从最上一层查找小于所指定元素的最右的节点,将节点入栈,然后转向下一层。
2 在当前层继续查找满足上述条件的节点,同样将其入栈,向下一层继续查找,直到找到最下一层满足条件的节点。
在查找的时候,从最上层开始找,因为最顶层元素最少,比较次数也少,如果在较上层找到了,直接返回 true ;如果在较上层比较时发现了更大的元素,或者到头了,说明再往后是找不见的,直接向下层跳即可(所以这里需要记录访问的前一个元素是什么以进行向下的跳跃,可以多加一个元素在循环的时候记录前次访问的元素,或者使用双链表实现)。直到跳到最后一层还是没到,那就是找不到了,返回 false 。

复杂度

跳表的平均查找和插入时间复杂度都是O(log n),优于普通队列的O(n),。
空间复杂度O(n),它因为建立了多级索引,因此非常耗内存()。它的查找借鉴了二分查找。利用空间换时间,根据随机数建立多级索引

完整代码

  1. package com.github.linkedList;
  2. import java.util.Random;
  3. public class SkipList {
  4. /**
  5. * @Description 最高层数是16
  6. */
  7. private static final int MAX_LEVEL = 1 <<4;
  8. /**
  9. * @Description 链表最高层数
  10. */
  11. private int levelCount = 1;
  12. /**
  13. * 哨兵结点,哨兵结点的层数就是是跳表层数的最大值
  14. */
  15. private Node head = new Node(MAX_LEVEL);
  16. private Random r = new Random();
  17. public Node find(int value) {
  18. Node p = head;
  19. // 从最高层开始往下一直查找到最底下的原始数据层,
  20. for (int i = levelCount - 1; i >= 0; --i) {
  21. //横向遍历当前层,找到比查找数值小的前一节点,移动到下层再开始查找
  22. while (p.forwards[i] != null && p.forwards[i].data < value) {
  23. // 找到前一节点
  24. p = p.forwards[i];
  25. }
  26. }
  27. // 0 就是我们存储原始数据层,如果原始数据层的前一个节点的下一个值等于查找值就返回
  28. if (p.forwards[0] != null && p.forwards[0].data == value) {
  29. return p.forwards[0];
  30. } else {
  31. return null;
  32. }
  33. }
  34. /**
  35. * 优化了作者zheng的插入方法
  36. *
  37. * @param value
  38. */
  39. public void insert(int value) {
  40. int level = head.forwards[0] == null ? 1 : randomLevel();
  41. // 每次只增加一层,如果条件满足
  42. if (level > levelCount) {
  43. level = ++levelCount;
  44. }
  45. Node newNode = new Node(level);
  46. newNode.data = value;
  47. Node update[] = new Node[level];
  48. for (int i = 0; i < level; ++i) {
  49. update[i] = head;
  50. }
  51. Node p = head;
  52. // 从最大层开始查找,找到前一节点,通过--i,移动到下层再开始查找
  53. for (int i = levelCount - 1; i >= 0; --i) {
  54. while (p.forwards[i] != null && p.forwards[i].data < value) {
  55. // 找到前一节点
  56. p = p.forwards[i];
  57. }
  58. // levelCount > level,所以加上判断
  59. if (level > i) {
  60. update[i] = p;
  61. }
  62. }
  63. for (int i = 0; i < level; ++i) {
  64. newNode.forwards[i] = update[i].forwards[i];
  65. update[i].forwards[i] = newNode;
  66. }
  67. }
  68. /**
  69. * 优化了作者zheng的插入方法2
  70. *
  71. * @param value
  72. */
  73. public void insert2(int value) {
  74. int level = head.forwards[0] == null ? 1 : randomLevel();
  75. // 每次只增加一层,如果条件满足
  76. if (level > levelCount) {
  77. level = ++levelCount;
  78. }
  79. Node newNode = new Node(level);
  80. newNode.data = value;
  81. Node p = head;
  82. // 从最大层开始查找,找到前一节点,通过--i,移动到下层再开始查找
  83. for (int i = levelCount - 1; i >= 0; --i) {
  84. while (p.forwards[i] != null && p.forwards[i].data < value) {
  85. // 找到前一节点
  86. p = p.forwards[i];
  87. }
  88. // levelCount > level,所以加上判断
  89. if (level > i) {
  90. if (p.forwards[i] == null) {
  91. p.forwards[i] = newNode;
  92. } else {
  93. Node next = p.forwards[i];
  94. p.forwards[i] = newNode;
  95. newNode.forwards[i] = next;
  96. }
  97. }
  98. }
  99. }
  100. /**
  101. * 作者zheng的插入方法,未优化前,优化后参见上面insert()
  102. *
  103. * @param value
  104. * @param level 0 表示随机层数,不为0,表示指定层数,指定层数
  105. * 可以让每次打印结果不变动,这里是为了便于学习理解
  106. */
  107. public void insert(int value, int level) {
  108. // 随机一个层数
  109. if (level == 0) {
  110. //需要注意的是如果随机level=1,那么插入时间复杂度为On
  111. level = randomLevel();
  112. }
  113. // 创建新节点
  114. Node newNode = new Node(level);
  115. newNode.data = value;
  116. // 记录要更新的层数,表示新节点要更新到哪几层
  117. Node update[] = new Node[level];
  118. for (int i = 0; i < level; ++i) {
  119. update[i] = head;
  120. }
  121. /**
  122. *
  123. * 查找前一个节点
  124. * 1,说明:层是从下到上的,这里最下数据层是0,目前设置的索引层依次是1-15
  125. * 2,这里没有从已有数据最大层(编号最大)开始找,(而是随机层的最大层)导致有些问题。
  126. */
  127. Node p = head;
  128. for (int i = level - 1; i >= 0; --i) {
  129. while (p.forwards[i] != null && p.forwards[i].data < value) {
  130. p = p.forwards[i];
  131. }
  132. // 这里update[i]表示当前层节点的前一节点,因为要找到前一节点,才好插入数据
  133. update[i] = p;
  134. }
  135. // 将每一层节点和后面节点关联
  136. for (int i = 0; i < level; ++i) {
  137. // 记录当前层节点后面节点指针
  138. newNode.forwards[i] = update[i].forwards[i];
  139. // 前一个节点的指针,指向当前节点
  140. update[i].forwards[i] = newNode;
  141. }
  142. // 更新层高
  143. if (levelCount < level) levelCount = level;
  144. }
  145. public void delete(int value) {
  146. Node[] update = new Node[levelCount];
  147. Node p = head;
  148. for (int i = levelCount - 1; i >= 0; --i) {
  149. while (p.forwards[i] != null && p.forwards[i].data < value) {
  150. p = p.forwards[i];
  151. }
  152. update[i] = p;
  153. }
  154. if (p.forwards[0] != null && p.forwards[0].data == value) {
  155. for (int i = levelCount - 1; i >= 0; --i) {
  156. if (update[i].forwards[i] != null && update[i].forwards[i].data == value) {
  157. update[i].forwards[i] = update[i].forwards[i].forwards[i];
  158. }
  159. }
  160. }
  161. }
  162. /**
  163. * 随机 level 次,如果是奇数层数 +1,防止伪随机
  164. *
  165. * @return
  166. */
  167. private int randomLevel() {
  168. int level = 1;
  169. for (int i = 1; i < MAX_LEVEL; ++i) {
  170. if (r.nextInt() % 2 == 1) {
  171. level++;
  172. }
  173. }
  174. return level;
  175. }
  176. /**
  177. * 打印每个节点数据和最大层数
  178. */
  179. public void printAll() {
  180. Node p = head;
  181. while (p.forwards[0] != null) {
  182. System.out.print(p.forwards[0] + " ");
  183. p = p.forwards[0];
  184. }
  185. System.out.println();
  186. }
  187. /**
  188. * 跳表的节点,每个节点存储了当前节点数据和所在层数数据
  189. */
  190. public class Node {
  191. /**
  192. * 当前节点的数值 默认值选择了-1
  193. */
  194. private int data = -1;
  195. /**
  196. * 表示当前节点位置的下一个节点所有层的数据,从上层切换到下层,就是数组下标-1
  197. * forwards[3]表示当前节点在第三层的下一个节点。
  198. */
  199. private Node forwards[];
  200. public Node(int level) {
  201. forwards = new Node[level];
  202. }
  203. @Override
  204. public String toString() {
  205. StringBuilder builder = new StringBuilder();
  206. builder.append("{ data: ");
  207. builder.append(data);
  208. builder.append("; levels: ");
  209. builder.append(" }");
  210. return builder.toString();
  211. }
  212. }
  213. public static void main(String[] args) {
  214. SkipList list = new SkipList();
  215. list.insert(0, 1);
  216. list.insert(1, 2);
  217. list.insert(2, 1);
  218. list.insert(3, 4);
  219. list.insert(4, 1);
  220. list.insert(5, 2);
  221. list.insert(6, 1);
  222. list.insert(7, 1);
  223. list.insert(8, 3);
  224. Node node = list.find(7);
  225. for (int i=0;i<node.forwards.length;i++){
  226. System.out.println(i+":"+node.forwards[i].data);
  227. }
  228. // list.insert(2, 3);
  229. // list.insert(3, 2);
  230. // list.insert(4, 4);
  231. // list.insert(5, 10);
  232. // list.insert(6, 4);
  233. // list.insert(8, 5);
  234. // list.insert(7, 4);
  235. list.printAll();
  236. /**
  237. * 结果如下:
  238. * null:15-------
  239. * null:14-------
  240. * null:13-------
  241. * null:12-------
  242. * null:11-------
  243. * null:10-------
  244. * 5:9-------
  245. * 5:8-------
  246. * 5:7-------
  247. * 5:6-------
  248. * 5:5-------
  249. * 5:4------- 8:4-------
  250. * 4:3-------5:3-------6:3-------7:3-------8:3-------
  251. * 1:2-------2:2------- 4:2-------5:2-------6:2-------7:2-------8:2-------
  252. * 1:1-------2:1-------3:1-------4:1-------5:1-------6:1-------7:1-------8:1-------
  253. * 1:0-------2:0-------3:0-------4:0-------5:0-------6:0-------7:0-------8:0-------
  254. * { data: 1; levels: 3 } { data: 2; levels: 3 } { data: 3; levels: 2 } { data: 4; levels: 4 }
  255. * { data: 5; levels: 10 } { data: 6; levels: 4 } { data: 7; levels: 4 } { data: 8; levels: 5 }
  256. */
  257. // 优化后insert()
  258. // SkipList list2 = new SkipList();
  259. // list2.insert2(1);
  260. // list2.insert2(2);
  261. // list2.insert2(6);
  262. // list2.insert2(7);
  263. // list2.insert2(8);
  264. // list2.insert2(3);
  265. // list2.insert2(4);
  266. // list2.insert2(5);
  267. // System.out.println();
  268. // list2.printAll_beautiful();
  269. }
  270. }

1206. 设计跳表

1506_skiplist.gif

ConcurrentSkipListMap

参考

https://juejin.im/post/5c7bcc9e6fb9a049a5719f70
https://juejin.im/post/57fa935b0e3dd90057c50fbc
https://juejin.im/post/5cdc38236fb9a0322d04ac7b
https://juejin.im/post/5cf8c50ff265da1b76389223
https://www.01hai.com/note/av127380