1. 跳表的用途
常用平衡数据结果有平衡二叉树(AVL Tree,红黑树(RBTree),B+树和B-树等,但是这些数据结果都比较复杂,很多人一下都不能直接写出正确的程序,而跳表恰好相反,跳表也是一种平衡的数据结果,它的搜索效率和平衡二叉树和红黑树差不多,不过跳表操作的代码却非常简单,只要你熟悉操作普通链表,就能够快速学会操作跳表。目前的软件 Redis和LevelDB都有用到它。
2.跳表的表示:
下面的结构是就是跳表:
其中 -1 表示 INT_MIN, 链表的最小值,1 表示 INT_MAX,链表的最大值。
跳表具有如下性质:
(1) 由很多层结构组成
(2) 每一层都是一个有序的链表
(3) 最底层(Level 1)的链表包含所有元素
(4) 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
3.跳表的搜索:
例子:查找元素 117
(1) 比较 21, 比 21 大,往后面找
(2) 比较 37, 比 37大,比链表最大值小,从 37 的下面一层开始找
(3) 比较 71, 比 71 大,比链表最大值小,从 71 的下面一层开始找
(4) 比较 85, 比 85 大,从后面找
(5) 比较 117, 等于 117, 找到了节点。
具体的搜索算法如下:
/* 如果存在 x, 返回 x 所在的节点, * 否则返回 x 的后继节点 */ find(x) { p = top; while (1) { while (p->next->key < x) p = p->next; if (p->down == NULL) return p->next; p = p->down; } }4.跳表的插入:
先确定该元素要占据的层数 K(采用丢硬币的方式,这完全是随机的)
然后在 Level 1 ... Level K 各个层的链表都插入元素。
例子:插入 119, K = 2
如果 K 大于链表的层数,则要添加新的层
例子:插入 119, K = 4
int random_level() { K = 1; while (random(0,1)) K++; return K; }
相当与做一次丢硬币的实验,如果遇到正面,继续丢,遇到反面,则停止,用实验中丢硬币的次数 K 作为元素占有的层数。显然随机变量 K 满足参数为 p = 1/2 的几何分布, K 的期望值 E[K] = 1/p = 2. 就是说,各个元素的层数,期望值是 2 层。5.跳表的高度: n 个元素的跳表,每个元素插入的时候都要做一次实验,用来决定元素占据的层数K,跳表的高度等于这 n 次实验中产生的最大 K,待续。6.跳表的空间复杂度分析: 根据上面的分析,每个元素的期望高度为 2, 一个大小为 n 的跳表,其节点数目的期望值是 2n7.跳表的删除: 在各层中找到相应的结点,并全部分删除即可
8. 在Java中,jdk已经为我们封装好了跳表这个数据结果,主要实现的类有:ConcurrentSkipListMap 和 ConcurrentSkipListSet 这两类,其中ConcurrentSkipListSet是在CocurrentSkipListMap的基础上实现的,示例代码如下:
package com.tds.commons; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListSet; /** * 跳表 * @author Administrator * */ public class SkipListTest { public static void main(String[] args) { ConcurrentSkipListMap<String, String> skipListMap = new ConcurrentSkipListMap<String, String>(); ConcurrentSkipListSet<String> skipListSet = new ConcurrentSkipListSet<String>(); skipListSet.add("123"); skipListSet.add("3"); skipListSet.add("2"); skipListSet.add("1"); skipListSet.add("12"); skipListSet.add("3"); for (String str : skipListSet) { System.out.println(str); } skipListMap.put("123", "123"); skipListMap.put("3", "123"); skipListMap.put("2", "123"); skipListMap.put("1", "123"); skipListMap.put("12", "123"); skipListMap.put("3", "456"); for (String str : skipListMap.keySet()) { System.out.println(str + ": " + skipListMap.get(str)); } } }
1
12
123
2
3
1: 123
12: 123
123: 123
2: 123
3: 456
在数据特别大,对性能有要求的,内存空间足够的情况下,用跳表是一种不错的选择