图示
在跳表中,第0层为原始的链表。第1层为第1级索引,第2层为第2级索引…层数越高,该层的节点越少。
在跳表中进行查找时,从最高层开始查找。比如如果我们需要查找10这个元素,就会经过以下路线:第二层的1->第一层的1->第一层的4->第一层的7->第一层的9->原始链表的9->原始链表的10。
由于从最高层(节点最少)的层开始查找,因此当数据较多时,相比普通的线性查找,跳表的查找可以快速缩小查找范围(有点类似于二分,但不完全是)。查找得时间复杂度是O(logn)。
代码实现
上图是一个单链表,为了便于删除,下面的代码是用双链表实现的。注释写得比较详细了。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class SkipListMultiSet {
static class Node {
Integer num;
int cnt;
Node[] forwards = new Node[TIER_NUM];
Node[] prevs = new Node[TIER_NUM];
public Node(Integer num, int cnt) {
this.num = num;
this.cnt = cnt;
}
@Override
public String toString() {
return "{" +
"num=" + num +
", cnt=" + cnt +
'}';
}
}
private static final int TIER_NUM = 30;
private final Node head = new Node(null, 0);
private final Node tail = new Node(null, 0);
public SkipListMultiSet() {
for (int i = 0; i < head.forwards.length; i++) {
head.forwards[i] = tail;
tail.prevs[i] = head;
}
}
public boolean search(int target) {
Node targetNode = this.findNode(target);
return targetNode != null && targetNode.cnt > 0;
}
public void add(int num) {
Node targetNode = findNode(num);
if (targetNode != null) {
targetNode.cnt++;
return;
}
Node[] nodeListToInsertAfter = getNodeListToInsertAfter(num);
Node newNode = new Node(num, 1);
insertAfter(newNode, nodeListToInsertAfter[0], 0);
for (int i = 1; i < nodeListToInsertAfter.length; i++) {
double d = Math.random();
if (d < 0.5) {
if (nodeListToInsertAfter[i].forwards[i] == newNode) {
continue;
}
insertAfter(newNode, nodeListToInsertAfter[i], i);
} else {
break;
}
}
}
public void insertAfter(Node newNode, Node anchor, int tier) {
Node originalAnchorNext = anchor.forwards[tier];
anchor.forwards[tier] = newNode;
newNode.forwards[tier] = originalAnchorNext;
originalAnchorNext.prevs[tier] = newNode;
newNode.prevs[tier] = anchor;
}
private Node[] getNodeListToInsertAfter(int num) {
Node[] nodeToInsertAfter = new Node[TIER_NUM];
int tier = TIER_NUM - 1;
Node ptr = head;
while (tier >= 0) {
Node next = ptr.forwards[tier];
if (next == tail && tier == 0) {
nodeToInsertAfter[tier] = ptr;
break;
} else if (next == tail) {
nodeToInsertAfter[tier] = ptr;
tier--;
} else if (next.num < num) {
ptr = next;
} else if (next.num > num) {
nodeToInsertAfter[tier] = ptr;
tier--;
} else if (next.num == num) {
nodeToInsertAfter[tier] = next;
tier--;
}
}
return nodeToInsertAfter;
}
public boolean erase(int num) {
Node targetNode = findNode(num);
if (targetNode == null) {
return false;
} else {
if (targetNode.cnt > 0) {
targetNode.cnt--;
if (targetNode.cnt == 0) {
removeThisNode(targetNode);
}
return true;
} else {
return false;
}
}
}
private void removeThisNode(Node anchor) {
for (int tier = 0; tier < TIER_NUM; tier++) {
Node preAnchor = anchor.prevs[tier];
Node postAnchor = anchor.forwards[tier];
if (preAnchor != null && postAnchor != null) {
preAnchor.forwards[tier] = postAnchor;
postAnchor.prevs[tier] = preAnchor;
}
}
}
private Node findNode(int num) {
int tier = TIER_NUM - 1;
Node ptr = head;
while (tier >= 0) {
Node next = ptr.forwards[tier];
if (next == tail && tier == 0) {
return null;
} else if (next == tail) {
tier--;
} else if (next.num < num) {
ptr = next;
} else if (next.num > num) {
tier--;
} else if (next.num == num) {
return next;
}
}
return null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
int tier = TIER_NUM - 1;
while (tier >= 0) {
List<Node> li = new ArrayList<>();
Node ptr = head;
while (ptr != null) {
li.add(ptr);
ptr = ptr.forwards[tier];
}
String line = li.stream().map(Object::toString).collect(Collectors.joining("<->"));
builder.append(line);
builder.append("\n");
tier--;
}
return builder.toString();
}
}
|