文章转自:http://www.acmerblog.com/skip-list-impl-java-5773.html
跳跃表是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间),并且对并发算法友好。
关于跳跃表的具体介绍可以参考MIT的公开课:跳跃表
跳跃表的应用
Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点,从而提高了效率。
在Java的API中已经有了实现:分别是
ConcurrentSkipListMap(在功能上对应HashTable、HashMap、TreeMap) ;
ConcurrentSkipListSet(在功能上对应HashSet).
确切来说,SkipList更像Java中的TreeMap,TreeMap基于红黑树(一种自平衡二叉查找树)实现的,时间复杂度平均能达到O(log n)。
HashMap是基于散列表实现的,时间复杂度平均能达到O(1)。ConcurrentSkipListMap是基于跳表实现的,时间复杂度平均能达到O(log n)。
Skip list的性质
(1) 由很多层结构组成,level是通过一定的概率随机产生的。
(2) 每一层都是一个有序的链表,默认是升序
(3) 最底层(Level 1)的链表包含所有元素。
(4) 如果一个元素出现在Level i 的链表中,则它在Level i 之下的链表也都会出现。
(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
Ø ConcurrentSkipListMap具有Skip list的性质 ,并且适用于大规模数据的并发访问。多个线程可以安全地并发执行插入、移除、更新和访问操作。与其他有锁机制的数据结构在巨大的压力下相比有优势。
Ø TreeMap插入数据时平衡树采用严格的旋转(比如平衡二叉树有左旋右旋)来保证平衡,因此Skip list比较容易实现,而且相比平衡树有着较高的运行效率。
Java代码实现:
1. SkipListEntry.java , 这是跳跃表中存储的每个元素实体类,包含 上下左右 四个指针。
01 | package skiplist; |
02 |
03 | public class SkipListEntry { |
04 | public String key; |
05 | public Integer value; |
06 |
07 | public int pos; //主要为了打印 链表用 |
08 |
09 | public SkipListEntry up, down, left, right; // 上下左右 四个指针 |
10 |
11 | public static String negInf = new String( "-oo" ); // 负无穷 |
12 | public static String posInf = new String( "+oo" ); // 正无穷 |
13 |
14 | public SkipListEntry(String k, Integer v) { |
15 | key = k; |
16 | value = v; |
17 |
18 | up = down = left = right = null ; |
19 | } |
20 |
21 | public Integer getValue() { |
22 | return value; |
23 | } |
24 |
25 | public String getKey() { |
26 | return key; |
27 | } |
28 |
29 | public Integer setValue(Integer val) { |
30 | Integer oldValue = value; |
31 | value = val; |
32 | return oldValue; |
33 | } |
34 |
35 | public boolean equals(Object o) { |
36 | SkipListEntry ent; |
37 | try { |
38 | ent = (SkipListEntry) o; // 检测类型 |
39 | } catch (ClassCastException ex) { |
40 | return false ; |
41 | } |
42 | return (ent.getKey() == key) && (ent.getValue() == value); |
43 | } |
44 |
45 | public String toString() { |
46 | return "(" + key + "," + value + ")" ; |
47 | } |
48 | } |
2. SkipList.java, 跳跃表类,包含算法的实现。 head 和 tail 分别是 顶层的头和尾。
001 | package skiplist; |
002 |
003 | import java.util.*; |
004 |
005 | public class SkipList { |
006 | public SkipListEntry head; // 顶层的第一个元素 |
007 | public SkipListEntry tail; // 顶层的最后一个元素 |
008 |
009 | public int n; // 跳跃表中的元素个数 |
010 |
011 | public int h; // 跳跃表的高度 |
012 | public Random r; // 投掷硬币 |
013 |
014 | public SkipList() // 默认构造函数... |
015 | { |
016 | SkipListEntry p1, p2; |
017 |
018 | p1 = new SkipListEntry(SkipListEntry.negInf, null ); |
019 | p2 = new SkipListEntry(SkipListEntry.posInf, null ); |
020 |
021 | head = p1; |
022 | tail = p2; |
023 |
024 | p1.right = p2; |
025 | p2.left = p1; |
026 |
027 | n = 0 ; |
028 | h = 0 ; |
029 | r = new Random(); |
030 | } |
031 |
032 | /** 返回 包含的元素个数 */ |
033 | public int size() { |
034 | return n; |
035 | } |
036 |
037 | /** 跳跃表是否为空 */ |
038 | public boolean isEmpty() { |
039 | return (n == 0 ); |
040 | } |
041 |
042 | //在最下面一层,找到要插入的位置前面的那个key |
043 | public SkipListEntry findEntry(String k) { |
044 | SkipListEntry p; |
045 | p = head; |
046 |
047 | while ( true ) { |
048 | /** |
049 | * 一直向右找,例: k=34. |
050 | * 10 ---> 20 ---> 30 ---> 40 ^ | p 会在30处停止 |
051 | * -------------------------------------------- |
052 | ***/ |
053 | while (p.right.key != SkipListEntry.posInf |
054 | && p.right.key.compareTo(k) <= 0 ) { |
055 | p = p.right; |
056 | // System.out.println(">>>> " + p.key); |
057 | } |
058 | // 如果还有下一层,就到下一层继续查找 |
059 | if (p.down != null ) { |
060 | p = p.down; |
061 | //System.out.println("vvvv " + p.key); |
062 | } else |
063 | break ; // 到了最下面一层 就停止查找 |
064 | } |
065 |
066 | return (p); // p.key <= k |
067 | } |
068 |
069 | /** 返回和key绑定的值 */ |
070 | public Integer get(String k) { |
071 | SkipListEntry p; |
072 |
073 | p = findEntry(k); |
074 |
075 | if (k.equals(p.getKey())) |
076 | return (p.value); |
077 | else |
078 | return ( null ); |
079 | } |
080 |
081 | /** 放一个key-value到跳跃表中, 替换原有的并返回 */ |
082 | public Integer put(String k, Integer v) { |
083 | SkipListEntry p, q; |
084 | int i; |
085 |
086 | p = findEntry(k); |
087 |
088 | if (k.equals(p.getKey())) { |
089 | Integer old = p.value; |
090 | p.value = v; |
091 | return (old); |
092 | } |
093 |
094 | q = new SkipListEntry(k, v); |
095 | q.left = p; |
096 | q.right = p.right; |
097 | p.right.left = q; |
098 | p.right = q; |
099 |
100 | i = 0 ; // 当前层 level = 0 |
101 |
102 | while (r.nextDouble() < 0.5 ) { |
103 |
104 | //如果超出了高度,需要重新建一个顶层 |
105 | if (i >= h) { |
106 | SkipListEntry p1, p2; |
107 |
108 | h = h + 1 ; |
109 | p1 = new SkipListEntry(SkipListEntry.negInf, null ); |
110 | p2 = new SkipListEntry(SkipListEntry.posInf, null ); |
111 |
112 | p1.right = p2; |
113 | p1.down = head; |
114 |
115 | p2.left = p1; |
116 | p2.down = tail; |
117 |
118 | head.up = p1; |
119 | tail.up = p2; |
120 |
121 | head = p1; |
122 | tail = p2; |
123 | } |
124 |
125 | while (p.up == null ) { |
126 | p = p.left; |
127 | } |
128 | p = p.up; |
129 |
130 | SkipListEntry e; |
131 |
132 | e = new SkipListEntry(k, null ); |
133 | e.left = p; |
134 | e.right = p.right; |
135 | e.down = q; |
136 |
137 | p.right.left = e; |
138 | p.right = e; |
139 | q.up = e; |
140 |
141 | q = e; // q 进行下一层迭代 |
142 | i = i + 1 ; // 当前层 +1 |
143 |
144 | } |
145 | n = n + 1 ; |
146 |
147 | return ( null ); // No old value |
148 | } |
149 |
150 | public Integer remove(String key) { |
151 | return ( null ); |
152 | } |
153 |
154 | public void printHorizontal() { |
155 | String s = "" ; |
156 | int i; |
157 | SkipListEntry p; |
158 |
159 | p = head; |
160 |
161 | while (p.down != null ) { |
162 | p = p.down; |
163 | } |
164 |
165 | i = 0 ; |
166 | while (p != null ) { |
167 | p.pos = i++; |
168 | p = p.right; |
169 | } |
170 |
171 | p = head; |
172 | while (p != null ) { |
173 | s = getOneRow(p); |
174 | System.out.println(s); |
175 |
176 | p = p.down; |
177 | } |
178 | } |
179 |
180 | //用了打印测试 |
181 | public String getOneRow(SkipListEntry p) { |
182 | String s; |
183 | int a, b, i; |
184 |
185 | a = 0 ; |
186 |
187 | s = "" + p.key; |
188 | p = p.right; |
189 |
190 | while (p != null ) { |
191 | SkipListEntry q; |
192 |
193 | q = p; |
194 | while (q.down != null ) |
195 | q = q.down; |
196 | b = q.pos; |
197 |
198 | s = s + " <-" ; |
199 |
200 | for (i = a + 1 ; i < b; i++) |
201 | s = s + "--------" ; |
202 |
203 | s = s + "> " + p.key; |
204 |
205 | a = b; |
206 |
207 | p = p.right; |
208 | } |
209 |
210 | return (s); |
211 | } |
212 |
213 | //用了打印测试 |
214 | public void printVertical() { |
215 | String s = "" ; |
216 | SkipListEntry p; |
217 | p = head; |
218 | while (p.down != null ) |
219 | p = p.down; |
220 |
221 | while (p != null ) { |
222 | s = getOneColumn(p); |
223 | System.out.println(s); |
224 |
225 | p = p.right; |
226 | } |
227 | } |
228 | //用了打印测试 |
229 | public String getOneColumn(SkipListEntry p) { |
230 | String s = "" ; |
231 | while (p != null ) { |
232 | s = s + " " + p.key; |
233 | p = p.up; |
234 | } |
235 | return (s); |
236 | } |
237 | } |
测试类,Test.java
01 | package skiplist; |
02 |
03 | public class Test1 { |
04 | public static void main(String[] args) { |
05 | SkipList S = new SkipList(); |
06 |
07 | S.printHorizontal(); |
08 | System.out.println( "------" ); |
09 | // S.printVertical(); |
10 | // System.out.println("======"); |
11 |
12 | S.put( "ABC" , 123 ); |
13 | S.printHorizontal(); |
14 | System.out.println( "------" ); |
15 | // S.printVertical(); |
16 | //System.out.println("======"); |
17 |
18 | S.put( "DEF" , 123 ); |
19 | S.printHorizontal(); |
20 | System.out.println( "------" ); |
21 | // S.printVertical(); |
22 | // System.out.println("======"); |
23 |
24 | S.put( "KLM" , 123 ); |
25 | S.printHorizontal(); |
26 | System.out.println( "------" ); |
27 | // S.printVertical(); |
28 | // System.out.println("======"); |
29 |
30 | S.put( "HIJ" , 123 ); |
31 | S.printHorizontal(); |
32 | System.out.println( "------" ); |
33 | // S.printVertical(); |
34 | // System.out.println("======"); |
35 |
36 | S.put( "GHJ" , 123 ); |
37 | S.printHorizontal(); |
38 | System.out.println( "------" ); |
39 | // S.printVertical(); |
40 | // System.out.println("======"); |
41 |
42 | S.put( "AAA" , 123 ); |
43 | S.printHorizontal(); |
44 | System.out.println( "------" ); |
45 | // S.printVertical(); |
46 | // System.out.println("======"); |
47 |
48 | } |
49 | } |
输出结果:
01 | -oo <-> +oo |
02 | ------ |
03 | -oo <-> ABC <-> +oo |
04 | -oo <-> ABC <-> +oo |
05 | ------ |
06 | -oo <-> ABC <---------> +oo |
07 | -oo <-> ABC <-> DEF <-> +oo |
08 | ------ |
09 | -oo <-> ABC <---------> KLM <-> +oo |
10 | -oo <-> ABC <-> DEF <-> KLM <-> +oo |
11 | ------ |
12 | -oo <-----------------> HIJ <---------> +oo |
13 | -oo <-> ABC <---------> HIJ <-> KLM <-> +oo |
14 | -oo <-> ABC <-> DEF <-> HIJ <-> KLM <-> +oo |
15 | ------ |
16 | -oo <-------------------------> HIJ <---------> +oo |
17 | -oo <-> ABC <-----------------> HIJ <-> KLM <-> +oo |
18 | -oo <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo |
19 | ------ |
20 | -oo <---------------------------------> HIJ <---------> +oo |
21 | -oo <---------> ABC <-----------------> HIJ <-> KLM <-> +oo |
22 | -oo <-> AAA <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo |
23 | ------ |
每次运行的结果是不一样的,这就是为什么说跳跃表是属于随机化数据结构。