简述
跳表的本质是同时维护了多个链表,并且链表是分层的,最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。
eg. 在跳表中查找元素 18
查询、插入、删除的时间复杂度都是O(logn)。
在单链表中,一旦定位到要插入的位置,那么插入节点的时间复杂度很低 O(1),但是查找插入位置比较消耗时间。对于跳表而言,查找的时间复杂度是 O(logn),所以查找某个数据应该插入的位置时间复杂度也是 O(logn),删除同样。
注意: 当不停的在跳表中增加数据,如不更新索引,极端的情况下,会出现两个索引节点之间数据非常多的情况,退化成单链表
跳表是通过随机函数来维护平衡的。
当在跳表中插入数据的时候,同时将这个数据插入到部分索引层中,如何选择索引层,可以通过一个随机函数来决定这个节点插入到哪几级索引中,比如随机生成了k,那么就将这个索引加入到,第一级到第k级索引中。
跳表使用的是空间换时间的思想,通过构建多级索引来提高查询效率,实现基于链表的“二分查找”,跳表是一种动态的数据结构,支持快速的查找、插入和删除操作,时间复杂度是 O(logn)。
跳表的空间复杂度是 O(n),不过跳表可以通过改变索引策略,动态的平衡执行效率和内存消耗。
源码如下
1. Node节点
class SkipListEntry{
// data
public String key;
public Integer value;
// links
public SkipListEntry up;
public SkipListEntry down;
public SkipListEntry left;
public SkipListEntry right;
// special
public static final String negInf = "-oo"; //用来处理一些特殊节点的初始化的
public static final String posInf = "+oo";
// constructor
public SkipListEntry(String key, Integer value) {
this.key = key;
this.value = value;
}
// methods...
}
2. 跳跃表本身
public class SkipList {
public SkipListEntry head; // First element of the top level
public SkipListEntry tail; // Last element of the top level
public int n; // number of entries in the Skip List
public int h; // Height
public Random r; // 决定新添加的节点是否能够向更高一层的链表攀升
}
3. findEntry():定位一个元素所在位置
private SkipListEntry findEntry(String key) {
SkipListEntry p;
p = head; // 从head头节点开始查找
while(true) {
// 从左向右查找,直到右节点的key值大于要查找的key值
while(p.right.key != SkipListEntry.posInf && p.right.key.compareTo(key) <= 0) {
p = p.right;
}
// 如果有更低层的节点,则向低层移动
if(p.down != null) {
p = p.down;
} else {
break;
}
}
// 返回p,!注意这里p的key值是小于等于传入key的值的(p.key <= key)
return p;
}
注:如果传入的key值在跳跃表中存在,则findEntry返回该对象的底层节点;
如不存在,则findEntry返回跳跃表中key值小于key,并且key值相差最小的底层节点;
4. get()方法
public Integer get(String key) {
SkipListEntry p;
p = findEntry(key);
if(p.key.equals(key)) {
return p.value;
} else {
return null;
}
}
5. put()方法
public Integer put(String key, Integer value) {
SkipListEntry p, q;
int i = 0;
// 查找适合插入的位子
p = findEntry(key);
// 如果跳跃表中存在含有key值的节点,则进行value的修改操作即可完成
if(p.key.equals(key)) {
Integer oldValue = p.value;
p.value = value;
return oldValue;
}
// 如果跳跃表中不存在含有key值的节点,则进行新增操作
q = new SkipListEntry(key, value);
q.left = p;
q.right = p.right;
p.right.left = q;
p.right = q;
// 再使用随机数决定是否要向更高level攀升
while(r.nextDouble() < 0.5) {
// 如果新元素的级别已经达到跳跃表的最大高度,则新建空白层
if(i >= h) {
addEmptyLevel();
}
// 从p向左扫描含有高层节点的节点
while(p.up == null) {
p = p.left;
}
p = p.up;
// 新增和q指针指向的节点含有相同key值的节点对象
// 这里需要注意的是除底层节点之外的节点对象是不需要value值的
SkipListEntry z = new SkipListEntry(key, null);
z.left = p;
z.right = p.right;
p.right.left = z;
p.right = z;
z.down = q;
q.up = z;
q = z;
i = i + 1;
}
n = n + 1;
// 返回null,没有旧节点的value值
return null;
}
注:只要随机数满足条件,key=42的节点就会一直向上攀升,直到它的level等于跳跃表的高度(height)。这个时候需要在跳跃表的最顶层添加一个空白层,同时跳跃表的height+1,