介绍
在二分查找中,我们需要对数组进行随机访问,而在普通的单链表中,我们只能从头到尾遍历,即使单链表是有序的。
跳跃表是有序的数据结构,在每个节点维持了多个指向其他节点的指针,达到快速访问节点的目的。
跳跃表查找的时间复杂度最坏为O(N),平均O(logN),大部分情况可以和平衡二叉树媲美,而且实现更简单。
本文的实现和数据结构的定义参照了这篇论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》
如上图:a就是最普通的单链表,在单链表的基础上进行修改,使得某些节点有指向后面其他节点的指针,跳过了单链表的下一个节点,直接指向了后面的节点,就形成的跳跃表。这样的好处是我们可以在高层更快地遍历节点。
数据结构的定义
public class SkipList {
//节点
class SkipListNode {
//向前的指针
SkipListNode[] forward;
//键
Comparable key;
//数据
Object value;
SkipListNode(Comparable key, Object value) {
this.forward = new SkipListNode[maxLevel];
this.value = value;
this.key = key;
}
}
//头节点
private SkipListNode header;
//允许的最高层级
private int maxLevel;
//当前跳跃表的最高层级
private int level;
//有多少个元素
private int size;
}
跳跃表的节点可以看成有若干层(实际上是同一个节点),最下面是第一层,每一层的指针指向其他节点的同一层,并且有高层则必须有底层。
我们可以这样来得到一个跳跃表:在单链表中,遍历一个元素,让他有1/2的几率得到第二层,再1/2的几率得到的三层。。。。。。层数越高,几率越低,并且不能超过允许的最高层级,再用该节点的每一层的指针指向下一个有该层的节点,没有就直接指向空。这样,层级越高,跨度越大,跳跃越快。头节点拥有每一层级的指针,并且大于当前跳跃表的最高层级节点的层级指针指向空。
算法设计
查找
在查询一个节点的过程中,我们首先访问跳跃表头节点的最高层级,我们看指向的元素是否小于我们要找的元素,如果是,那么就向右移动,直到不小于或者下一个为空为止。如果该层级的下一个元素不为空并且等于要找的元素,那么可以直接返回数据,否则,向下移动一个层级,再重复上述过程,直到找到,或者不能再向下移动了返回空。
例如:上面图的e,我们找17。
- 我们在头节点的第四层,下一个是6,小于17,向右移动
- 在6的第四层,右边是空,向下移动
- 在6的第三层,右边是25,大于17,向下移动
- 在6的第二层,右边是9, 小于17,向右移动
- 在9的第二层,右边是17,返回17的结果
插入
插入的过程中,我们用类似查找的方法找到元素在第一层的应当放置的位置,即左边小,右边大,如果在过程中发现了该元素,那么将新的数据替换掉原来的数据。生成一个新的节点,我们先定下它的层级,拥有第二层的几率是1/2,拥有第三层的几率是1/4。。。。。。另外,插入一个元素我们可能需要改变左边节点的指针的指向(当生成的节点的层级高于该指针时),我们用update数组来记录需要被改变的节点,以及改变哪一层。每一层其实只有一个节点需要改变该层的指针,那么我们用update[i]存放可能要改变的节点,让它的第i+1层指针指向新的节点,如果生成的节点层级大于当前最高层级,我们还要把header放入对应的update位置中,如下图
如上图所示:我们需要在上面的结构中插入17的节点,我们在寻找的过程中,记录下了update = [12, 9, 6, 6],意思是6的第四层和第三层,9的第二层,和12的第一层可能需要改变指针指向,而我们生成的17只有两层,那么就只改9和12。在生成17节点后,我们将17节点每一层的指针指向update数组对应层的节点的原来的下一个节点,比如第二层update[1] = 节点9,我们就将节点17的第二层指向原来节点9的原本下一个节点L节点25,然后再改变节点9的指向,让它指向17节点。
删除
删除的过程和插入的过程相似,同样用查找的方法来遍历,直到找到或者最后找不到,并且过程中也记录update数组,让其在节点删除后能够指向被删除节点的下一个
代码实现
@SuppressWarnings("unchecked")
public class SkipList {
class SkipListNode {
SkipListNode[] forward;
Comparable key;
Object value;
SkipListNode(Comparable key, Object value) {
this.forward = new SkipListNode[maxLevel];
this.value = value;
this.key = key;
}
}
private SkipListNode header;
private int maxLevel;
private int level;
private int size;
public SkipList() {
this(4);
}
public SkipList(int maxLevel) {
this.maxLevel = maxLevel;
this.header = new SkipListNode(null, null);
this.level = 1;
}
public boolean insert(Comparable newKey, Object newValue) {
SkipListNode current = this.header;
SkipListNode[] update = new SkipListNode[maxLevel];
for (int i = this.level-1 ; i >= 0 ; i--) {
while (current.forward[i] != null && current.forward[i].key.compareTo(newKey) < 0) {
current = current.forward[i];
}
update[i] = current;
//相同则替换
if (current.forward[i] != null && current.forward[i].key.compareTo(newKey) == 0) {
current.forward[i].key = newKey;
current.forward[i].value = newValue;
return true;
}
}
int lvl = this.getRandomLevel();
//大于当前最高层级,需要把更高级的header放入update数组来改变指针指向
if (lvl > this.level) {
for (int i = this.level ; i <= lvl-1 ; i++) {
update[i] = this.header;
}
this.level = lvl;
}
current = new SkipListNode(newKey, newValue);
for (int i = 0 ; i < lvl ; i++) {
current.forward[i] = update[i].forward[i];
update[i].forward[i] = current;
}
this.size++;
return true;
}
private int getRandomLevel() {
int res = 1;
while (Math.random() < 0.5 && res < this.maxLevel) {
res = res+1;
}
return res;
}
public Object get(Comparable key) {
SkipListNode current = this.header;
for (int i = this.level - 1 ; i >= 0 ; i--) {
while (current.forward[i] != null && current.forward[i].key.compareTo(key) < 0) {
current = current.forward[i];
}
if (current.forward[i] != null && current.forward[i].key.compareTo(key) == 0) {
return current.forward[i].value;
}
}
return null;
}
public boolean remove(Comparable key) {
SkipListNode current = this.header;
SkipListNode[] update = new SkipListNode[maxLevel];
for (int i = this.level - 1 ; i >= 0 ; i--) {
while (current.forward[i] != null && current.forward[i].key.compareTo(key) < 0) {
current = current.forward[i];
}
update[i] = current;
}
current = current.forward[0];
if (current != null && current.key.compareTo(key) == 0) {
//一直从下到上修改指向,直到某一层不是指向要删除的节点
for (int i = 0 ; i < this.level; i++) {
if (update[i].forward[i] != current){
break;
}
update[i].forward[i] = current.forward[i];
}
//删除可能导致层级变化
while (this.level > 1 && this.header.forward[level - 1] == null) {
this.level--;
}
this.size--;
return true;
} else {
return false;
}
}
public int getSize() {
return this.size;
}
//可以用来打印看看跳跃表的结构
public void printValues() {
for (int i = this.level - 1 ; i >= 0 ; i--) {
SkipListNode current = this.header.forward[i];
while (current != null) {
System.out.print(current.value+"--->");
current = current.forward[i];
}
System.out.println("NIL");
}
}
}
测试
public static void main(String[] args) {
SkipList list = new SkipList();
list.insert(1,1);
list.insert(3,3);
list.insert(2,2);
list.insert(7,7);
list.insert(5,5);
list.insert(4,4);
list.printValues();
System.out.println("result: "+list.get(5));
list.insert(8,55);
list.printValues();
System.out.println("result: "+list.get(8));
list.remove(8);
list.printValues();
System.out.println("result: "+list.get(8));
}
7--->NIL
7--->NIL
3--->7--->NIL
1--->2--->3--->4--->5--->7--->NIL
result: 5
7--->NIL
7--->NIL
3--->7--->NIL
1--->2--->3--->4--->5--->7--->55--->NIL
result: 55
7--->NIL
7--->NIL
3--->7--->NIL
1--->2--->3--->4--->5--->7--->NIL
result: null