跳表的结构可以看成是多层链表,但只是链多,节点不多(可以想象成几根竹签(链)并排穿起几个鱼豆腐块长条(节点)😋)。每一个节点都是竖长条状的,也包含 key 和 value,但底层不是树结构,比较好实现。
跳表结构
-
一个节点
<key, value>
会被几条链串起来,不是由 key 本身来决定的,而是随机的:-
随机函数只会随机 roll 出
0
和1
,roll 的过程中,会求出当前节点的层数level
(即链的条数),level
初始为0
(最少只有1层,从0开始计数),当 roll 出0时,level++
;roll 出1时,计算结束,此时level
就是当前节点的层数。 -
由随机函数可知,一个节点有 1 、 2 、 3 、 … 、 n 1、2、3、…、n 1、2、3、…、n层的概率是 1 2 、 1 4 、 1 8 、 . . . 、 1 2 n \frac{1}{2}、\frac{1}{4}、\frac{1}{8}、...、\frac{1}{2^n} 21、41、81、...、2n1,即处在上层的节点是逐渐减少的(随机方法有很多,上面的只是一种方法)。
-
-
最底层是一条包含所有节点的单链表,上面的链表规模依次减少
-
链表会有一个头结点,这个头结点是默认的,它比其他所有的节点都要小,所以排在最前面。而且该节点的高度也是当前链表 roll 出的最高层。
跳表操作
1、查找
-
每次查找都是从最上面的链表开始找,依次往下走。
-
判断当前层所处位置的下一个节点与要找节点的大小关系:
- 如果下一个节点大于要找节点,则去往下一层的链表继续查找
- 如果下一个节点小于要找节点,则去往下一个节点继续查找
- 如果等于,则直接返回
-
比如现在要找 30 这个节点:
我们可以看到,借助上层链表,我们跳过了很多节点,起到加速的作用;整体的查询复杂度可以达到 O ( l o g N ) O(log~N) O(log N)
2、插入
-
首先我们会 roll 出要插入节点的层数,然后开始查找该节点插入的位置。
-
整个查找过程其实与上面的查找操作类似,只不过需要从 roll 出的层开始查找,而且必须要深入到最后一层链表(为了修改底层的链表)
-
以插入 14 为例,假设 roll 出了
2
:
3、删除
- 首先判断跳表中是否存在要删除的元素,如果有则通过查找操作找到那个节点
- 从头结点的顶层开始,找到该层恰好小于 要删除元素的节点:
- 如果该节点的当前层数小于等于删除节点的层数,则需要修改指针的指向,让该节点的指针指向删除节点的下一个节点,然后去往下一层
- 否则,直接去往下一层
代码实现
public class SkipListMap {
/**
* 跳表的节点类
*/
public static class SkipListNode {
// 属性一:该节点代表的值
public Integer value;
// 属性二:该节点所指向的node列表
public ArrayList<SkipListNode> nextNodes;
public SkipListNode(Integer value) {
this.value = value;
nextNodes = new ArrayList<>();
}
}
/**
* 调表迭代器,本质就是遍历最后一层链表
*/
public static class SkipListIterator implements Iterator<Integer> {
SkipList list;
SkipListNode current;
public SkipListIterator(SkipList list) {
this.list = list;
this.current = list.getHead();
}
@Override
public boolean hasNext() {
return current.nextNodes.get(0) != null;
}
@Override
public Integer next() {
current = current.nextNodes.get(0);
return current.value;
}
}
/**
* 跳表类
*/
public static class SkipList {
// 填表的头节点
private final SkipListNode head;
// 调表的最大高度
private int maxLevel;
// 当前容量
private int size;
private static final double PROBABILITY = 0.5;
public SkipList() {
size = 0;
maxLevel = 0;
head = new SkipListNode(null);
head.nextNodes.add(null);
}
/**
* 获取头节点
*/
public SkipListNode getHead() {
return head;
}
/**
* 向跳表中添加元素
*/
public void add(Integer newValue) {
// 如果当前跳表里面不包含这个newValue
if (!contains(newValue)) {
// 向跳表里面添加节点,size增长
size++;
int level = 0;
// 随机出层数
while (Math.random() < PROBABILITY) {
level++;
}
// 如果随机出来的level层数大于最大层数maxLevel,则maxLevel增加
while (level > maxLevel) {
head.nextNodes.add(null);
maxLevel++;
}
// 创建该newValue对应的跳表
SkipListNode newNode = new SkipListNode(newValue);
SkipListNode current = head;
do {
// 针对每一层,找到第一个比newValue小的地方
current = findNext(newValue, current, level);
// 将当前跳表接单的第一个加上current的下一个节点,以这种方式将跳表全部连接起来
newNode.nextNodes.add(0, current.nextNodes.get(level));
// 将current的当前层节点放上newValue的节点
current.nextNodes.set(level, newNode);
} while (level-- > 0);
}
// 如果跳表中存在此value,不进行任何操作
}
/**
* 删除元素
*/
public void delete(Integer deleteValue) {
if (contains(deleteValue)) {
SkipListNode deleteNode = find(deleteValue);
size--;
int level = maxLevel;
SkipListNode current = head;
do {
current = findNext(deleteNode.value, current, level);
if (deleteNode.nextNodes.size() > level) {
// 删除操作直接取消连接就可以了
current.nextNodes.set(level, deleteNode.nextNodes.get(level));
}
} while (level-- > 0);
}
}
/**
* 返回跳表中,小于等于 e 的最大值
*/
private SkipListNode find(Integer e) {
// 给定元素e,查询其所在的跳表节点
return find(e, head, maxLevel);
}
/**
* 从节点current的level层开始,查找小于等于 e 的最大值并返回
*/
private SkipListNode find(Integer e, SkipListNode current, int level) {
do {
current = findNext(e, current, level);
} while (level-- > 0);
// 一直查到最下面一层
return current;
}
/**
* 返回在当前 level 层,小于 e 的最大值
*/
private SkipListNode findNext(Integer e, SkipListNode current, int level) {
// 首先获取当前层的下一个节点
SkipListNode next = current.nextNodes.get(level);
while (next != null) {
Integer value = next.value;
// e < value 如果当前e小于下一层的节点值,则直接break,进入下一层
if (lessThan(e, value)) {
break;
}
current = next;
next = current.nextNodes.get(level);
}
// 如果下一层为null,直接返回给父函数find(),进入下一层
return current;
}
public int size() {
return size;
}
// 检查跳表中是否存在此value
public boolean contains(Integer value) {
// 首先根据指定的值查询value在跳表种存在的节点
SkipListNode node = find(value);
// 查到的节点值和当前的值一样,则表示已经查到了
return node != null && node.value != null && equalTo(node.value, value);
}
public Iterator<Integer> iterator() {
return new SkipListIterator(this);
}
private boolean lessThan(Integer a, Integer b) {
return a.compareTo(b) < 0;
}
private boolean equalTo(Integer a, Integer b) {
return a.compareTo(b) == 0;
}
}
}