链表有一个严重的缺陷:需要顺序扫描来定位所查找的元素。查找从表头开始,若成功,则停在被查元素所在的位置,或是在没有找到这个元素时到达表尾。表中元素有序时可以加快查找速度,但仍需顺序查找。因此我们开始构思一种允许跳过某些节点以避免顺序操作的表。跳转表(skip list)是一种有趣的有序链表的变体,它可以使这种非顺序查找成为可能。
跳转表的结构如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/ac22168d98cee8b32b14a7e375616e6e.gif)
图一
上图这只不过是一种可能的节点的结构,它的结构是很不规律的。原因一下说明,这跟链表的构造有关系。
从上图可以看出,如果查找一个元素,并不需要顺序查找(虽然这也是有可能的),可以直接跳过某些节点,因为总体是有序的,而且能够直接定位到内部的节点。
一说到上面的那些性质,很自然的想到一些组织很规则的表,比如:而每隔两个节点都指向两个位置前的节点,每隔四个位置指向四个位置前的节点,如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/b653a0cb8c06e4da890576f812ece824.gif)
图二
可以看出,跳转表的查找是很高效的。然而,跳转表的设计(像上面的规则的设计,图二)可能导致插入和删除过程极其低效。插入一个新节点时,所有在其后的节点都必须重新组织:引用域的个数和引用值都要改变。要保留跳转表再查找方面的某些优点,同时避免插入和删除的重组织问题,需要放弃不同层次节点的位置要求,没有结构规则的限制,也就没有重组织的问题了。像图一所示。
我认为,跳转表的形式应该大致相同,只不过是实现的方式各异而已。我看书上的算法是这样的:确定一个最高层次maxLevel(这个最高层次对整个链表的结构影响很大),根据maxLevel生成一个数组powers,里面存放[1,9,13,15]四个数(如果maxLevel=5,则powers=[1,17,15,29,31]),是每一层的界限,它根据生成的随机数,确定应该把待插入的元素插入到第几层。插入时,生成一个1~15的随机数r,如果r在1~8之间,则插入到第一层,如果在9~12之间,插入到第二层,如果是13或14,插入到第三层,15则插入到第四层,然后根据插入到的层次数,生成一个节点,这个节点也有同样的引用域数量。
具体程序如下:
import java.util.Random;
//节点类
class IntSkipListNode {
public int key;
public IntSkipListNode[] next;
IntSkipListNode(int key, int num) {
this.key = key;
next = new IntSkipListNode[num];
for (int i = 0; i < num; i++)
next[i] = null;
}
}
//链表类
public class IntSkipList {
private int maxLevel;
private IntSkipListNode[] root;
private int[] powers;
private Random rd = new Random();
IntSkipList() {
this(4);
}
IntSkipList(int num) {
maxLevel = num;
root = new IntSkipListNode[maxLevel];
powers = new int[maxLevel];
for (int i = 0; i < maxLevel; i++)
root[i] = null;
choosePowers();
}
public boolean isEmpty() {
return root[0] == null;
}
//初始化数组power,设定每一层的数值范围的最低界限
public void choosePowers() {
powers[maxLevel - 1] = (2 << (maxLevel - 1)) - 1;
for (int i = maxLevel - 2, j = 0; i >= 0; i--, j++)
powers[i] = powers[i + 1] - (2 << j);
}
//生成一个随机数,根据这个随机数,来判断新插入的结点应该放到哪一层上。
public int chooseLevel() {
int ranInt = Math.abs(rd.nextInt());
int i, r = ranInt % powers[maxLevel - 1] + 1;
for (i = 1; i < maxLevel; i++)
if (r < powers[i])
return i - 1;
return i - 1;
}
//找一个结点,找到了,返回这个数,没有找到,返回0
public int skipListSearch(int key) {
int lvl;
IntSkipListNode prev, curr;
for (lvl = maxLevel - 1; lvl >= 0 && root[lvl] == null; lvl--) //从上面扫描,直至不为null的指针区域
;
prev = curr = root[lvl]; //指向最上面的那个指向不为null的结点
while (true) {
if (key == curr.key)
return curr.key; //找到了,返回这个数
else if (key < curr.key) { //找过了,往前找
if (lvl == 0)
return 0; //达到末尾了,返回0,表示没有找到。
else if (curr == root[lvl])
curr = root[--lvl];
else
curr = prev.next[--lvl];
} else { //还没到,接着往后找
prev = curr;
if (curr.next[lvl] != null) //后面还有
curr = curr.next[lvl];
else { //后面没有了,就应该回到主表(IntSkipList),取到下一个指针所指对象
for (lvl--; lvl >= 0 && curr.next[lvl] == null; lvl--) //找到下面第一个不为null的结点
;
if (lvl >= 0) //后面还有结点
curr = curr.next[lvl];
else //没有找到
return 0;
}
}
}
}
//插入一个表中不存在的元素
public void skipListInsert(int key) {
IntSkipListNode[] curr = new IntSkipListNode[maxLevel];
IntSkipListNode[] prev = new IntSkipListNode[maxLevel];
IntSkipListNode newNode;
int lvl;
curr[maxLevel - 1] = root[maxLevel - 1];
prev[maxLevel - 1] = null;
//使prev指向最后一个小于key的结点,以便在它后面插入key结点;并使curr指向第一个大于
//key的结点,以便使curr所指的结点加入到存储key的结点的next中
for (lvl = maxLevel - 1; lvl >= 0; lvl--) {
while (curr[lvl] != null && curr[lvl].key < key) {
prev[lvl] = curr[lvl];
curr[lvl] = curr[lvl].next[lvl];
}
if (curr[lvl] != null && curr[lvl].key == key)
return;
if (lvl > 0) {
if (prev[lvl] == null) {
curr[lvl - 1] = root[lvl - 1];
prev[lvl - 1] = null;
} else {
curr[lvl - 1] = prev[lvl].next[lvl - 1];
prev[lvl - 1] = prev[lvl];
}
}
}
lvl = chooseLevel(); //随机选择该结点所处的层数
newNode = new IntSkipListNode(key, lvl + 1);
for (int i = 0; i <= lvl; i++) {
newNode.next[i] = curr[i];
if (prev[i] == null)
root[i] = newNode;
else
prev[i].next[i] = newNode;
}
}
public static void main(String[] args) {//测试
IntSkipList isl = new IntSkipList(4);
isl.skipListInsert(5);
isl.skipListInsert(17);
isl.skipListInsert(35);
isl.skipListInsert(7);
isl.skipListInsert(8);
isl.skipListInsert(12);
isl.skipListInsert(19);
isl.skipListInsert(10);
isl.skipListInsert(22);
isl.skipListInsert(31);
isl.skipListInsert(28);
isl.skipListInsert(33);
if (!isl.isEmpty()) {
System.out.println(isl.skipListSearch(20));
System.out.println(isl.skipListSearch(33));
}
}
}
写的很乱,没办法,能力至此。这种结构好像不是很常见,但是我觉得很巧妙。
出自:《数据结构与算法——Java语言版(第二版)》