参考内容

[1] 📑 论文:https://epaperpress.com/sortsearch/download/skiplist.pdf
[2] 📙 漫画:https://www.jianshu.com/p/dc252b5efca6
[3] 🚀 博客:https://logi.im/algorithm/skiplist.html
[4] 💤 知乎:https://zhuanlan.zhihu.com/p/107835889

跳表概述

SkipList(跳跃表)来源于William Pugh于1990年发表的Skip Lists: A Probabilistic Alternative toBalanced Trees。

  • 跳跃表是由多层有序链表组合而成的,最底一层的链表保存了所有的数据,每向上的一层链表依次保存了下一层链表的部分数据。
  • 相邻的两层链表中元素相同的节点之间存在引用关系,一般是上层节点中存在一个指向下层节点的引用。
  • 跳跃表的目的在于提高了查询效率,同时也牺牲了一定的存储空间。

设计初衷:在某些场景下替代红黑树。

对比二叉查找树:维持结构平衡的成本比较低,完全依靠随机,并发性更强。而二叉查找树在多次插入删除后,需要rebalance来重新调整结构平衡。

算法简介

插入过程
方向:自底向上。
根据指定的随机Level向上层添加节点。
skiplist_insertions.png
每一个节点的层数(level)是随机出来的,而且新插入一个节点不会影响其他节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。

查找过程
方向:自顶向下,从左向右。
先在最上一层链表中从左向右查找,然后依次向下查找,直到最下一层的某个节点结束。
image.png

复杂度分析

假设总结点数为n,每2个节点间构建一个索引,少于2个节点的层无意义

层次 节点数
top 2
k n / 2k
2 n / 22
1 n / 2
0 n

SkipList - 图3
加上最后一层,则总层数为SkipList - 图4,1 ~ top层需要占用额外空间,节点数总和为
SkipList - 图5
所以跳表的空间复杂度为O(n)

时间复杂度
插入和删除依赖查找,调整指针的时间复杂度为O(1)
如果像上面那样建立最密索引,每层只需一次比较,因为层数为SkipList - 图6,所以最好时间复杂度为O(logn)
不建立索引退化为链表,最差时间复杂度为O(n)

Java实现

  1. /**
  2. * @author KHighness
  3. * @since 2021-07-22
  4. * @apiNote 跳跃表
  5. */
  6. public class SkipList<E extends Comparable<? super E>> {
  7. private final static class Node<S> {
  8. private final static class Level<T> {
  9. /** 层后继 */
  10. Node<T> backward;
  11. /** 层跨度 */
  12. int span;
  13. public Level(Node<T> backward, int span) {
  14. this.backward = backward;
  15. this.span = span;
  16. }
  17. }
  18. /** 数据 */
  19. S item;
  20. /** 前驱 */
  21. Node<S> forward;
  22. /** 层级数组 */
  23. Level<S>[] levels;
  24. public Node(S item, int level) {
  25. this.item = item;
  26. this.levels = new Level[level];
  27. }
  28. }
  29. private static final int DEFAULT_LEVEL = 6;
  30. /** 表头 */
  31. private final Node<E> head;
  32. /** 表尾 */
  33. private Node<E> tail;
  34. /** 最大层级 */
  35. private final int maxLevel;
  36. /** 总节点数 */
  37. private long size;
  38. public SkipList() {
  39. this(DEFAULT_LEVEL);
  40. }
  41. public SkipList(int maxLevel) {
  42. this.head = new Node<>(null, maxLevel);
  43. this.tail = new Node<>(null, maxLevel);
  44. this.maxLevel = maxLevel;
  45. for (int i = 0; i < maxLevel; i++) {
  46. head.levels[i] = new Node.Level<>(null, 0);
  47. }
  48. }
  49. /**
  50. * 查找并返回第level层小于elem的最后一个节点
  51. * <p>同一level,从左向右
  52. *
  53. * @param start 起始搜寻点
  54. * @param level 当前层级
  55. * @param elem 比较数据
  56. * @return 查找节点
  57. */
  58. private Node<E> getLevelPrev(Node<E> start, int level, E elem) {
  59. while (start.levels[level].backward != null &&
  60. start.levels[level].backward.item.compareTo(elem) < 0) {
  61. start = start.levels[level].backward;
  62. }
  63. return start;
  64. }
  65. /**
  66. * 查找并返回包含指定数据的节点
  67. * <p>自顶向下,从左向右
  68. *
  69. * @param elem 待查数据
  70. * @return 找到则返回数据所在节点,未找到返回null
  71. */
  72. private Node<E> getNode(E elem) {
  73. Node<E> prev = head;
  74. for (int i = maxLevel - 1; i >= 0; i--) {
  75. // 获取当前层级的前驱
  76. prev = getLevelPrev(prev, i, elem);
  77. // 确保存在后继
  78. if (prev.levels[i] == null) continue;
  79. Node<E> node = prev.levels[i].backward;
  80. if (node != null && node.item.equals(elem)) {
  81. return node;
  82. }
  83. }
  84. return null;
  85. }
  86. /**
  87. * 生成并返回一个随机层级
  88. *
  89. * @return [1, maxLevel]之间的随机数
  90. */
  91. private int getRandomLevel() {
  92. return (int) Math.ceil(Math.random() * (maxLevel - 1)) + 1;
  93. }
  94. /**
  95. * 查找数据是否存在
  96. *
  97. * @param elem 待查数据
  98. * @return true 代表存在,false 代表不存在
  99. */
  100. public boolean contains(E elem) {
  101. return getNode(elem) != null;
  102. }
  103. /**
  104. * 插入数据
  105. * <ol>
  106. * <li>根据新数据新建一个节点
  107. * <li>生成节点的随机高度level
  108. * <li>对于[1, level]每一层,设置层次数组中节点的后继和跨度
  109. * <li>在最底层即第1层,插入节点,维护前驱和后继的关系
  110. * </ol>
  111. *
  112. * @param elem 待插数据
  113. * @return true 代表插入成功,false 代表数据已存在
  114. */
  115. public boolean insert(E elem) {
  116. if (contains(elem)) return false;
  117. int level = getRandomLevel();
  118. Node<E> newNode = new Node<>(elem, level);
  119. Node<E> prev = head;
  120. for (int i = level - 1; i >= 0; i--) {
  121. // 获得当前层级的前驱
  122. prev = getLevelPrev(prev, i, elem);
  123. Node.Level<E> prevLevel = prev.levels[i];
  124. Node<E> next = prevLevel.backward;
  125. // 第一层,将新节点插入到前驱和后继的中间,维护关系
  126. if (i == 0) {
  127. if (next != null) {
  128. next.forward = newNode;
  129. } else {
  130. tail = newNode;
  131. }
  132. newNode.forward = prev;
  133. }
  134. // 增加后继的跨度
  135. while (next != null) {
  136. next.levels[i].span++;
  137. next = next.levels[i].backward;
  138. }
  139. // 设置新节点的后继和跨度
  140. newNode.levels[i] = new Node.Level<>(prevLevel.backward,
  141. prevLevel.span + 1);
  142. // 更新前驱的后继
  143. prevLevel.backward = newNode;
  144. }
  145. size++;
  146. return true;
  147. }
  148. /**
  149. * 删除数据
  150. * <ol>
  151. * <li>查找节点,获取到节点的层次数组的长度level
  152. * <li>对于[1, level]每一层,更新层级数组中节点前驱的后继
  153. * <li>在最底层即第1层,删除结点,维护前驱和后继的关系
  154. * </ol>
  155. *
  156. * @param elem 待删数据
  157. * @return true 代表删除成功,false 代表数据不存在
  158. */
  159. public boolean delete(E elem) {
  160. Node<E> node = getNode(elem);
  161. if (node == null) return false;
  162. Node<E> prev = head;
  163. int level = node.levels.length;
  164. for (int i = level - 1; i >= 0; i--) {
  165. // 获得当前层级的前驱
  166. prev = getLevelPrev(prev, i, elem);
  167. Node<E> next = node.levels[i].backward;
  168. // 更新前驱的后继
  169. prev.levels[i].backward = next;
  170. // 更新后继的前驱或尾节点
  171. if (i == 0) {
  172. if (next != null) {
  173. next.forward = prev;
  174. } else {
  175. tail = prev;
  176. }
  177. }
  178. // 减少后继的跨度
  179. while (next != null) {
  180. next.levels[i].span--;
  181. next = next.levels[i].backward;
  182. }
  183. }
  184. size--;
  185. return true;
  186. }
  187. public void print() {
  188. int maxItemLength = size == 0 ? 0 : tail.item.toString().length();
  189. String format = "-> |%" + maxItemLength + "s| ";
  190. StringBuilder gap = new StringBuilder("------");
  191. for (int i = 0; i < maxItemLength; i++) gap.append('-');
  192. System.out.printf("Size of list: %d\n", size);
  193. for (int i = maxLevel - 1; i >= 0; i--) {
  194. Node<E> node = head;
  195. int nextSpan = 1;
  196. if (node.levels[i].backward != null) {
  197. nextSpan = node.levels[i].backward.levels[i].span;
  198. }
  199. System.out.printf("|L%d| ", i);
  200. while (node.levels[i].backward != null) {
  201. node = node.levels[i].backward;
  202. while (nextSpan++ < node.levels[0].span) {
  203. System.out.print(gap);
  204. }
  205. System.out.printf(format, node.item);
  206. }
  207. for (int j = nextSpan; j <= tail.levels[0].span; j++) {
  208. System.out.print(gap);
  209. }
  210. System.out.println("-> |NULL|");
  211. }
  212. }
  213. }

测试结果

测试代码:

  1. /**
  2. * @author KHighness
  3. * @since 2021-07-22
  4. * @apiNote 跳跃表测试
  5. */
  6. public class Main {
  7. public static void main(String[] args) {
  8. SkipList<Integer> list = new SkipList<>(7);
  9. for (int i = 0; i < 10; i++) {
  10. // [10, 100)
  11. int item = (int) (Math.random() * 90) + 10;
  12. list.insert(item);
  13. }
  14. list.print();
  15. System.out.printf("\nInsertion of 88 succeeded? %s\n", list.insert(88));
  16. System.out.printf("Insertion of 55 succeeded? %s\n", list.insert(55));
  17. System.out.printf("Insertion of 11 succeeded? %s\n", list.insert(11));
  18. System.out.printf("Insertion of 11 again succeeded? %s\n\n", list.insert(11));
  19. list.print();
  20. System.out.printf("\nDeletion of 88 succeeded? %s\n", list.delete(88));
  21. System.out.printf("Deletion of 55 succeeded? %s\n", list.delete(55));
  22. System.out.printf("Deletion of 11 succeeded? %s\n", list.delete(11));
  23. System.out.printf("Deletion of 11 again succeeded? %s\n\n", list.delete(11));
  24. list.print();
  25. }
  26. }

运行结果:

  1. Size of list: 10
  2. |L6| ---------> |23| -------------------------> |58| ---------------------------------> |NULL|
  3. |L5| ---------> |23| -----------------> |46| -> |58| ---------------------------------> |NULL|
  4. |L4| ---------> |23| ---------> |43| -> |46| -> |58| ---------------------------------> |NULL|
  5. |L3| ---------> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -------------------------> |NULL|
  6. |L2| -> |12| -> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -> |61| -----------------> |NULL|
  7. |L1| -> |12| -> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -> |61| -> |62| -> |65| -> |NULL|
  8. |L0| -> |12| -> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -> |61| -> |62| -> |65| -> |NULL|
  9. Insertion of 88 succeeded? true
  10. Insertion of 55 succeeded? true
  11. Insertion of 11 succeeded? true
  12. Insertion of 11 again succeeded? false
  13. Size of list: 13
  14. |L6| -> |11| ---------> |23| -------------------------> |55| -> |58| -----------------------------------------> |NULL|
  15. |L5| -> |11| ---------> |23| -----------------> |46| -> |55| -> |58| -----------------------------------------> |NULL|
  16. |L4| -> |11| ---------> |23| ---------> |43| -> |46| -> |55| -> |58| -----------------------------------------> |NULL|
  17. |L3| -> |11| ---------> |23| -> |35| -> |43| -> |46| -> |55| -> |58| -> |60| ---------------------------------> |NULL|
  18. |L2| -> |11| -> |12| -> |23| -> |35| -> |43| -> |46| -> |55| -> |58| -> |60| -> |61| -------------------------> |NULL|
  19. |L1| -> |11| -> |12| -> |23| -> |35| -> |43| -> |46| -> |55| -> |58| -> |60| -> |61| -> |62| -> |65| -> |88| -> |NULL|
  20. |L0| -> |11| -> |12| -> |23| -> |35| -> |43| -> |46| -> |55| -> |58| -> |60| -> |61| -> |62| -> |65| -> |88| -> |NULL|
  21. Deletion of 88 succeeded? true
  22. Deletion of 55 succeeded? true
  23. Deletion of 11 succeeded? true
  24. Deletion of 11 again succeeded? false
  25. Size of list: 10
  26. |L6| ---------> |23| -------------------------> |58| ---------------------------------> |NULL|
  27. |L5| ---------> |23| -----------------> |46| -> |58| ---------------------------------> |NULL|
  28. |L4| ---------> |23| ---------> |43| -> |46| -> |58| ---------------------------------> |NULL|
  29. |L3| ---------> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -------------------------> |NULL|
  30. |L2| -> |12| -> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -> |61| -----------------> |NULL|
  31. |L1| -> |12| -> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -> |61| -> |62| -> |65| -> |NULL|
  32. |L0| -> |12| -> |23| -> |35| -> |43| -> |46| -> |58| -> |60| -> |61| -> |62| -> |65| -> |NULL|
  33. Process finished with exit code 0