跳表是由William Pugh于1990年发明,他在论文中给出了跳表的描述:
Skip lists are a data structure that can be used in place of balanced trees. Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.
跳表是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。
与单向链表不同,每一个结点不单单只包含指向下一个结点的指针,可能包含很多个指向后续结点的指针,这样就可以跳过一些不必要的结点,从而加快查找、删除等操作。对于一个链表内每一个结点包含多少个指向后续元素的指针,后续节点个数是通过一个随机函数生成器得到,这样子就构成了一个跳跃表。
随机生成的跳跃表如下图所示:
这基本上就是跳表的核心思想,其实也是一种通过空间来换取时间的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。目前开源软件 Redis 和 LevelDB 都有用到它。
其增删改查的时间复杂度为O(log n).
如果一个节点存在k个向前的指针的话,那么称该节点是k层的节点。
一个跳表的MaxLevel为跳表中所有节点中最大的层数。
public static class SkipListNode {
public Integer value;
public ArrayList<SkipListNode> nextNodes;
public SkipListNode(Integer value) {
this.value = value;
nextNodes = new ArrayList<SkipListNode>();
}
}
public static class SkipList {
private SkipListNode head;
private int maxLevel;
private int size;
private static final double PROBABILITY = 0.5;
}
PROBABILITY表示生成下一层的概率
public static class SkipList {
private 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;
}
}
以查找19为例,先找到6,再找到25,19比25小,在6和25之间;再找到9,再找到17,再找到25,19比25小,在17和25节点之间,17再下一个即为19。
public static class SkipList {
private SkipListNode head;
private int maxLevel;
private int size;
private static final double PROBABILITY = 0.5;
// Returns the skiplist node with greatest value <= e
private SkipListNode find(Integer e) {
return find(e, head, maxLevel);
}
// Returns the skiplist node with greatest value <= e
// Starts at node start and level
private SkipListNode find(Integer e, SkipListNode current, int level) {
do {
current = findNext(e, current, level);
} while (level-- > 0);
return current;
}
// Returns the node at a given level with highest value less than e
// 关键方法:找到给定level中value小于e的最大节点
private SkipListNode findNext(Integer e, SkipListNode current, int level) {
SkipListNode next = current.nextNodes.get(level);
while (next != null) {
Integer value = next.value;
if (lessThan(e, value)) { // e < value
break;
}
current = next;
next = current.nextNodes.get(level);
}
return current;
}
private boolean lessThan(Integer a, Integer b) {
return a.compareTo(b) < 0;
}
private boolean equalTo(Integer a, Integer b) {
return a.compareTo(b) == 0;
}
}
由于跳表数据结构整体上是有序的,所以在插入时,需要首先查找到合适的位置,然后就是修改指针(和链表中操作类似),然后更新跳表的level变量。
public static class SkipList {
private SkipListNode head;
private int maxLevel;
private int size;
private static final double PROBABILITY = 0.5;
public void add(Integer newValue) {
if (!contains(newValue)) {
size++;
int level = 0;
while (Math.random() < PROBABILITY) {
level++;
}
while (level > maxLevel) {
head.nextNodes.add(null);
maxLevel++;
}
SkipListNode newNode = new SkipListNode(newValue);
SkipListNode current = head;
do {
current = findNext(newValue, current, level);
newNode.nextNodes.add(0, current.nextNodes.get(level));
current.nextNodes.set(level, newNode);
} while (level-- > 0);
}
}
public boolean contains(Integer value) {
SkipListNode node = find(value);
return node != null && node.value != null && equalTo(node.value, value);
}
}
删除操作类似于插入操作,包含如下3步:查找到需要删除的结点、删除结点、调整指针。
public static class SkipList {
private SkipListNode head;
private int maxLevel;
private int size;
private static final double PROBABILITY = 0.5;
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);
}
}
}
遍历第0层的节点即可。
public static class SkipList {
private SkipListNode head;
private int maxLevel;
private int size;
private static final double PROBABILITY = 0.5;
// Inner Class
public static class SkipListIterator implements Iterator<Integer> {
SkipList list;
SkipListNode current;
public SkipListIterator(SkipList list) {
this.list = list;
this.current = list.getHead();
}
public boolean hasNext() {
return current.nextNodes.get(0) != null;
}
public Integer next() {
current = current.nextNodes.get(0);
return current.value;
}
}
public Iterator<Integer> iterator() {
return new SkipListIterator(this);
}
}
import java.util.ArrayList;
import java.util.Iterator;
public class SkipList_Simple {
public static class SkipListNode {
public Integer value;
public ArrayList<SkipListNode> nextNodes;
public SkipListNode(Integer value) {
this.value = value;
nextNodes = new ArrayList<SkipListNode>();
}
}
public static class SkipListIterator implements Iterator<Integer> {
SkipList list;
SkipListNode current;
public SkipListIterator(SkipList list) {
this.list = list;
this.current = list.getHead();
}
public boolean hasNext() {
return current.nextNodes.get(0) != null;
}
public Integer next() {
current = current.nextNodes.get(0);
return current.value;
}
}
public static class SkipList {
private 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) {
if (!contains(newValue)) {
size++;
int level = 0;
while (Math.random() < PROBABILITY) {
level++;
}
while (level > maxLevel) {
head.nextNodes.add(null);
maxLevel++;
}
SkipListNode newNode = new SkipListNode(newValue);
SkipListNode current = head;
do {
current = findNext(newValue, current, level);
newNode.nextNodes.add(0, current.nextNodes.get(level));
current.nextNodes.set(level, newNode);
} while (level-- > 0);
}
}
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);
}
}
// Returns the skiplist node with greatest value <= e
private SkipListNode find(Integer e) {
return find(e, head, maxLevel);
}
// Returns the skiplist node with greatest value <= e
// Starts at node start and level
private SkipListNode find(Integer e, SkipListNode current, int level) {
do {
current = findNext(e, current, level);
} while (level-- > 0);
return current;
}
// Returns the node at a given level with highest value less than e
private SkipListNode findNext(Integer e, SkipListNode current, int level) {
SkipListNode next = current.nextNodes.get(level);
while (next != null) {
Integer value = next.value;
if (lessThan(e, value)) { // e < value
break;
}
current = next;
next = current.nextNodes.get(level);
}
return current;
}
public int size() {
return size;
}
public boolean contains(Integer value) {
SkipListNode node = find(value);
return node != null && node.value != null && equalTo(node.value, value);
}
public Iterator<Integer> iterator() {
return new SkipListIterator(this);
}
/******************************************************************************
* Utility Functions *
******************************************************************************/
private boolean lessThan(Integer a, Integer b) {
return a.compareTo(b) < 0;
}
private boolean equalTo(Integer a, Integer b) {
return a.compareTo(b) == 0;
}
}
public static void main(String[] args) {
}
}
ConcurrentSkipListMap是JDK1.6以后提供的一个并发的SkipList。
从上面的构造我们可以看到,Index节点包含了Node节点,除此之外,Index还有两个指针,一个指向同一个layer的下一个节点right,一个指向下一层layer的节点down。采用自旋+CAS的方式进行增删改查。