跳表
1.定义
跳表(SkipList):增加了向前指针的链表叫做指针。跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质是一种可以进行二分查找的有序链表。跳表在原有的有序链表上增加了多级索引,通过索引来实现快速查询。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能
2.详解
对于一个单链表来讲,即便链表中存储的数据是有序的,如果要查询需要O(n)的时间复杂度
比如有一条原始链表 1->5->7->9->11->13
利用二分的思想来加快链表查询,我们可以每个几个链表节点建立一个索引,比如每两个建立一个索引
索引层1——>7——>11
↓ ↓ ↓
数据层1->5->7->9->11->13
我们发现要查询9,只用先查询索引层的1、7以及数据层的7与9,上面的例子知识建立了一个索引层,建立多个索引层也是可以的
索引层1——–——>11
↓ ↓ ↓
1——>7——>11
↓ ↓ ↓
数据层1->5->7->9->11->13
2.1时间复杂度空间复杂度分析
我们首先考虑这样一个问题,如果链表里有n个结点,那么会有多少级索引呢?按照上面讲的,每两个结点都会抽出一个结点作为上一级索引的结点。那么第一级索引的个数大约就是n/2,第二级的索引大约就是n/4,第三级的索引就是n/8,依次类推,也就是说,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那么第k级的索引结点个数为:。
假设索引有h级,最高级的索引有2个结点,通过上面的公式,我们可以得到,从而可得:h = 。如果包含原始链表这一层,整个跳表的高度就是。我们在跳表中查找某个数据的时候,如果每一层都要遍历m个结点,那么在跳表中查询一个数据的时间复杂度就为:O(m*logn)。m为每个该层相邻节点之间在下层的间距。
空间复杂度,对于2分链表 这几级索引结点总和为:n/2 + n/4 + n/8 + … + 8 + 4 + 2 = n - 2。所以跳表的空间复杂度为O(n)
2.2实现
Node的next为该节点存储在哪一层的节点,如Node.next[2]表示,Node在第三层的节点
// 跳表中存储的是正整数,并且存储的数据是不重复的
public class SkipList {
private static final int MAX_LEVEL = 16; // 结点的个数
private int levelCount = 1; // 索引的层级数
private Node head = new Node(); // 头结点
private Random random = new Random();
// 查找操作
public Node find(int value){
Node p = head;
for(int i = levelCount - 1; i >= 0; --i){
while(p.next[i] != null && p.next[i].data < value){
p = p.next[i];
}
}
if(p.next[0] != null && p.next[0].data == value){
return p.next[0]; // 找到,则返回原始链表中的结点
}else{
return null;
}
}
// 插入操作
public void insert(int value){
int level = randomLevel();
Node newNode = new Node();
newNode.data = value;
newNode.maxLevel = level; // 通过随机函数改变索引层的结点布置
Node update[] = new Node[level];
for(int i = 0; i < level; ++i){
update[i] = head;
}
Node p = head;
for(int i = level - 1; i >= 0; --i){
while(p.next[i] != null && p.next[i].data < value){
p = p.next[i];
}
update[i] = p;
}
for(int i = 0; i < level; ++i){
newNode.next[i] = update[i].next[i];
update[i].next[i] = newNode;
}
if(levelCount < level){
levelCount = level;
}
}
// 删除操作
public void delete(int value){
Node[] update = new Node[levelCount];
Node p = head;
for(int i = levelCount - 1; i >= 0; --i){
while(p.next[i] != null && p.next[i].data < value){
p = p.next[i];
}
update[i] = p;
}
if(p.next[0] != null && p.next[0].data == value){
for(int i = levelCount - 1; i >= 0; --i){
if(update[i].next[i] != null && update[i].next[i].data == value){
update[i].next[i] = update[i].next[i].next[i];
}
}
}
}
// 随机函数
private int randomLevel(){
int level = 1;
for(int i = 1; i < MAX_LEVEL; ++i){
if(random.nextInt() % 2 == 1){
level++;
}
}
return level;
}
// Node内部类
public class Node{
private int data = -1;
private Node next[] = new Node[MAX_LEVEL];
private int maxLevel = 0;
// 重写toString方法
@Override
public String toString(){
StringBuilder builder = new StringBuilder();
builder.append("{data:");
builder.append(data);
builder.append("; leves: ");
builder.append(maxLevel);
builder.append(" }");
return builder.toString();
}
}
// 显示跳表中的结点
public void display(){
Node p = head;
while(p.next[0] != null){
System.out.println(p.next[0] + " ");
p = p.next[0];
}
System.out.println();
}
}