跳跃表是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要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 , 这是跳跃表中存储的每个元素实体类,包含 上下左右 四个指针。
package skiplist;
public class SkipListEntry {
public String key;
public Integer value;
public int pos; //主要为了打印 链表用
public SkipListEntry up, down, left, right; // 上下左右 四个指针
public static String negInf = new String("-oo"); // 负无穷
public static String posInf = new String("+oo"); // 正无穷
public SkipListEntry(String k, Integer v) {
key = k;
value = v;
up = down = left = right = null;
}
public Integer getValue() {
return value;
}
public String getKey() {
return key;
}
public Integer setValue(Integer val) {
Integer oldValue = value;
value = val;
return oldValue;
}
public boolean equals(Object o) {
SkipListEntry ent;
try {
ent = (SkipListEntry) o; // 检测类型
} catch (ClassCastException ex) {
return false;
}
return (ent.getKey() == key) && (ent.getValue() == value);
}
public String toString() {
return "(" + key + "," + value + ")";
}
}
2. SkipList.java, 跳跃表类,包含算法的实现。 head 和 tail 分别是 顶层的头和尾。
package skiplist;
import java.util.*;
public class SkipList {
public SkipListEntry head; // 顶层的第一个元素
public SkipListEntry tail; // 顶层的最后一个元素
public int n; // 跳跃表中的元素个数
public int h; // 跳跃表的高度
public Random r; // 投掷硬币
public SkipList() // 默认构造函数...
{
SkipListEntry p1, p2;
p1 = new SkipListEntry(SkipListEntry.negInf, null);
p2 = new SkipListEntry(SkipListEntry.posInf, null);
head = p1;
tail = p2;
p1.right = p2;
p2.left = p1;
n = 0;
h = 0;
r = new Random();
}
/** 返回 包含的元素个数 */
public int size() {
return n;
}
/** 跳跃表是否为空 */
public boolean isEmpty() {
return (n == 0);
}
//在最下面一层,找到要插入的位置前面的那个key
public SkipListEntry findEntry(String k) {
SkipListEntry p;
p = head;
while (true) {
/**
* 一直向右找,例: k=34.
* 10 ---> 20 ---> 30 ---> 40 ^ | p 会在30处停止
* --------------------------------------------
***/
while (p.right.key != SkipListEntry.posInf
&& p.right.key.compareTo(k) <= 0) {
p = p.right;
// System.out.println(">>>> " + p.key);
}
// 如果还有下一层,就到下一层继续查找
if (p.down != null) {
p = p.down;
//System.out.println("vvvv " + p.key);
} else
break; // 到了最下面一层 就停止查找
}
return (p); // p.key <= k
}
/** 返回和key绑定的值 */
public Integer get(String k) {
SkipListEntry p;
p = findEntry(k);
if (k.equals(p.getKey()))
return (p.value);
else
return (null);
}
/** 放一个key-value到跳跃表中, 替换原有的并返回 */
public Integer put(String k, Integer v) {
SkipListEntry p, q;
int i;
p = findEntry(k);
if (k.equals(p.getKey())) {
Integer old = p.value;
p.value = v;
return (old);
}
q = new SkipListEntry(k, v);
q.left = p;
q.right = p.right;
p.right.left = q;
p.right = q;
i = 0; // 当前层 level = 0
while (r.nextDouble() < 0.5) {
//如果超出了高度,需要重新建一个顶层
if (i >= h) {
SkipListEntry p1, p2;
h = h + 1;
p1 = new SkipListEntry(SkipListEntry.negInf, null);
p2 = new SkipListEntry(SkipListEntry.posInf, null);
p1.right = p2;
p1.down = head;
p2.left = p1;
p2.down = tail;
head.up = p1;
tail.up = p2;
head = p1;
tail = p2;
}
while (p.up == null) {
p = p.left;
}
p = p.up;
SkipListEntry e;
e = new SkipListEntry(k, null);
e.left = p;
e.right = p.right;
e.down = q;
p.right.left = e;
p.right = e;
q.up = e;
q = e; // q 进行下一层迭代
i = i + 1; // 当前层 +1
}
n = n + 1;
return (null); // No old value
}
public Integer remove(String key) {
return (null);
}
public void printHorizontal() {
String s = "";
int i;
SkipListEntry p;
p = head;
while (p.down != null) {
p = p.down;
}
i = 0;
while (p != null) {
p.pos = i++;
p = p.right;
}
p = head;
while (p != null) {
s = getOneRow(p);
System.out.println(s);
p = p.down;
}
}
//用了打印测试
public String getOneRow(SkipListEntry p) {
String s;
int a, b, i;
a = 0;
s = "" + p.key;
p = p.right;
while (p != null) {
SkipListEntry q;
q = p;
while (q.down != null)
q = q.down;
b = q.pos;
s = s + " <-";
for (i = a + 1; i < b; i++)
s = s + "--------";
s = s + "> " + p.key;
a = b;
p = p.right;
}
return (s);
}
//用了打印测试
public void printVertical() {
String s = "";
SkipListEntry p;
p = head;
while (p.down != null)
p = p.down;
while (p != null) {
s = getOneColumn(p);
System.out.println(s);
p = p.right;
}
}
//用了打印测试
public String getOneColumn(SkipListEntry p) {
String s = "";
while (p != null) {
s = s + " " + p.key;
p = p.up;
}
return (s);
}
}
测试类,Test.java
package skiplist;
public class Test1 {
public static void main(String[] args) {
SkipList S = new SkipList();
S.printHorizontal();
System.out.println("------");
// S.printVertical();
// System.out.println("======");
S.put("ABC", 123);
S.printHorizontal();
System.out.println("------");
// S.printVertical();
//System.out.println("======");
S.put("DEF", 123);
S.printHorizontal();
System.out.println("------");
// S.printVertical();
// System.out.println("======");
S.put("KLM", 123);
S.printHorizontal();
System.out.println("------");
// S.printVertical();
// System.out.println("======");
S.put("HIJ", 123);
S.printHorizontal();
System.out.println("------");
// S.printVertical();
// System.out.println("======");
S.put("GHJ", 123);
S.printHorizontal();
System.out.println("------");
// S.printVertical();
// System.out.println("======");
S.put("AAA", 123);
S.printHorizontal();
System.out.println("------");
// S.printVertical();
// System.out.println("======");
}
}
输出结果:
-oo <-> +oo
------
-oo <-> ABC <-> +oo
-oo <-> ABC <-> +oo
------
-oo <-> ABC <---------> +oo
-oo <-> ABC <-> DEF <-> +oo
------
-oo <-> ABC <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <-> KLM <-> +oo
------
-oo <-----------------> HIJ <---------> +oo
-oo <-> ABC <---------> HIJ <-> KLM <-> +oo
-oo <-> ABC <-> DEF <-> HIJ <-> KLM <-> +oo
------
-oo <-------------------------> HIJ <---------> +oo
-oo <-> ABC <-----------------> HIJ <-> KLM <-> +oo
-oo <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
------
-oo <---------------------------------> HIJ <---------> +oo
-oo <---------> ABC <-----------------> HIJ <-> KLM <-> +oo
-oo <-> AAA <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
------