简介
跳跃表是一种单链表形式的链式结构,不同于一般的链式结构其为多层链式结构。正因为这种多层结构从而相比于单式结构的搜索性能得到了大幅提高。其搜索方式有点类似于二叉搜索所以理论搜索性能达到了 O(logn)。
一、理论证明
二、实现方式
如上图所示跳跃表一般有一个哨兵节点。该哨兵节点不是一个单节点,而是一组节点。节点的数目与跳跃表的层高保持一致。当插入一个节点时我们需要进行如下几步操作:
1, 寻找插入位置
2, 随机生成层高
3, 插入节点
1,插入
如上示例,在层高为6的跳跃表中插入元素3。我们从哨兵节点组的最高层寻找插入点。5,6两层的第一个节点值为4大于3,降低层高到第四层发现第一个元素为2小于3,所以将该元素插入节点2之后。
Node add(int i, Node w) {
Node u = sentinel;
int k = w.height();
int r = h;
int j = -1; // index of u
while (r >= 0) {
while (u.next[r] != null && j+u.length[r] < i) {
j += u.length[r];
u = u.next[r];
}
u.length[r]++; // to account for new node in list 0
if (r <= k) {
w.next[r] = u.next[r];
u.next[r] = w;
w.length[r] = u.length[r] - (i - j);
u.length[r] = i - j;
}
r--;
}
return u;
}
2,查询
查询与上述插入类似。从最高层开始寻找。如果搜索元素大于需要寻找的KEY,降低层高直到搜索到的元素不小于时退出。这时找到的元素为大于等于该KEY的值。再次比较下就OK了。
Node<T> findPredNode(T x) {
Node<T> u = sentinel;
int r = h;
while (r >= 0) {
while (u.next[r] != null && compare(u.next[r].x,x) < 0)
u = u.next[r]; // go right in list r
r--; // go down into list r-1
}
return u;
}
3,删除
删除的前提是查询,只有查到元素的具体位置才能删除;
boolean remove(T x) {
boolean removed = false;
Node<T> u = sentinel;
int r = h;
int comp = 0;
while (r >= 0) {
while (u.next[r] != null && (comp = compare(u.next[r].x, x)) < 0) {
u = u.next[r];
}
if (u.next[r] != null && comp == 0) {
removed = true;
u.next[r] = u.next[r].next[r];
if (u == sentinel && u.next[r] == null)
h--; // skiplist height has gone down
}
r--;
}
return removed;
}
三, SkipList VS Rb_Tree VS Hash
首先HASH算法无疑是效率最高的,但是HASH存在致命缺陷冲突。红黑树理论效率与SkipList一致但实现复杂度方面要高好几个量级,并且红黑树是无序的也就是说不能进行批量查询,在这方面跳跃表就有很好的优势。当年然跳跃表也有其劣势那就是内存占用率,其每个节点都需要消耗额外的节点维护其所在层的后驱。
四,使用场景
跳跃表可以使用在对内存不是很敏感且需要支持批量查询的场景。现实场景中LevelDB,Redis 便使用其作为查询数据结构。
refer:http://opendatastructures.org/versions/edition-0.1e/ods-java/Contents.html