目录
一、跳表的原理
对于单链表,即使是 存储的有序数据(即 有序链表),想在其中查找某个数据,也只能从头到尾遍历,查找效率低,时间复杂度是O(n),如下图所示:
为了提高查找效率,使用二分查找的思想,对有序链表建立一级“索引”。 每两个节点提取一个节点到索引层。 索引层中的每个节点 都包含两个指针,一个指向下一个节点,一个down指针,指向下一级节点。
假设要查找图中 18这个节点:
首先在一级索引层遍历,当遍历到14这个节点的时候,发现其下一个节点是23,则要查找的18就在14和23之间。 然后,通过14节点的 down 指针,下降到原始链表这一层,继续在原始链表中遍历。 此时,只需要在原始链表中,遍历两个节点,14和18,就找到18这个节点了。 查找18这个节点,在原始链表需要遍历10个节点,现在只需要遍历7个节点(一级索引层遍历5个节点,原始链表遍历2个节点)。
从以上示例可以看出,加上一层索引之后,查找一个节点的遍历节点数减少了,效率提高了。如果再增加一级索引,那么效率会不会更高呢?
建立二级索引
与建立一级索引的方式类似,在第一级索引的基础上,每两个节点抽出一个节点到第二级索引,如下图:
现在如果要查找18节点,只需要遍历6个节点(二级索引层遍历3个节点,一级索引层1个节点,原始链表2个节点)。
通过建立索引的方式,对于数据量越大的有序链表,通过建立多级索引,查找效率提升会非常明显。
这种链表加多级索引的结构 就是 跳表。
二、跳表的插入和删除
跳表作为一个动态数据结构,不仅支持查找操作,还支持数据的插入和删除操作,并且 插入和删除的操作的时间复杂度都是O(logn).
插入操作
为了保证原始链表中数据的有序性,我们需要先找到新数据应该插入的位置。 可以基于多级索引,快速查找到新数据的插入位置,时间复杂度为O(log n)。
假设插入数据为6的节点,如下图:
删除操作
删除原链表中的节点,如果节点存在于索引中,也要删除索引中的节点。 因为单链表中的删除需要用到 要删除节点 的 前驱动节点。 可以像插入操作一样,通过索引逐层向下遍历到原始链表中,要删除的节点,并记录其 前驱节点,从而实现删除操作。
三、跳表的代码实现
本代码是学习左神跳表视频时所写,供大家一起学习。
public class SkipListMapClass {
//跳表的节点定义
public static class SkipListNode<K extends Comparable<K>, V>{
public K key;
public V val;
public ArrayList<SkipListNode<K,V>> nextNodes;
public SkipListNode(K k,V v){
key = k;
val = v;
nextNodes = new ArrayList<SkipListNode<K,V>>();
}
// 遍历的时候,如果是往右遍历到的null(next == null),遍历结束
// 头(null),头节点的null认为是最小的
// node ->头,node(null," ") node.isKeyLess(!null) true
// node 里面的key是否比otherKey小,true,不是false
public boolean isKeyLess(K otherKey){
return otherKey != null && (key == null || key.compareTo(otherKey) < 0);
}
public boolean isKeyEqual(K otherKey){
return (key == null && otherKey == null) ||
(key != null && otherKey != null && key.compareTo(otherKey) == 0);
}
}
public static class SkipListMap<K extends Comparable<K>,V>{
private static final double PROBABILITY = 0.5;
private SkipListNode<K,V> head;
private int size;
private int maxLevel;
public SkipListMap(){
head = new SkipListNode<K,V>(null,null);
head.nextNodes.add(null); //最底层节点为0层节点
size = 0;
maxLevel = 0;
}
//从最高层开始,一路找下去
//最终,找到第0层的 < key 的最右的节点
private SkipListNode<K,V> mostRightLessNodeInTree(K key){
if (key == null){
return null;
}
int level = maxLevel;
SkipListNode<K,V> cur = head;
while (level >= 0){//从上层跳到下层
//cur level -> level-1
cur = mostRightLessNodeInLevel(key,cur,level--);
}
return cur;
}
//在level层里,往右移动
//现在来到的节点是cur,来到cur的level层,在level层上,找到 <key 最后一个节点并返回
private SkipListNode<K,V> mostRightLessNodeInLevel(K key,SkipListNode<K,V> cur,int level){
SkipListNode<K,V> next = cur.nextNodes.get(level);
while (next != null && next.isKeyLess(key)) {
cur = next;
next = cur.nextNodes.get(level);
}
return cur;
}
public boolean containKey(K key){
if (key == null){
return false;
}
SkipListNode<K,V> less = mostRightLessNodeInTree(key);
SkipListNode<K,V> next = less.nextNodes.get(0);
return next != null && next.isKeyEqual(key);
}
public void put(K key,V value){
if (key == null){
return;
}
//0层上,最右一个, < key 的Node
SkipListNode<K,V> less = mostRightLessNodeInTree(key);
SkipListNode<K,V> find = less.nextNodes.get(0);
if (find != null &&find.isKeyEqual(key)){//修改
find.val = value;
}else {//新增
size++;
int newNodeLevel = 0;
while(Math.random() < PROBABILITY){
newNodeLevel++;
}
while (newNodeLevel > maxLevel){
head.nextNodes.add(null);
maxLevel++;
}
SkipListNode<K,V> newNode = new SkipListNode<K,V>(key,value);
for (int i =0;i <=newNodeLevel;i++){
newNode.nextNodes.add(null);
}
int level = maxLevel;
SkipListNode<K,V> pre = head;
while (level >= 0){
pre = mostRightLessNodeInLevel(key,pre,level);
if (level <= newNodeLevel){
newNode.nextNodes.set(level,pre.nextNodes.get(level));
pre.nextNodes.set(level,newNode);
}
level--;
}
}
}
public V get(K key){
if (key == null){
return null;
}
SkipListNode<K,V> less = mostRightLessNodeInTree(key);
SkipListNode<K,V> next = less.nextNodes.get(0);
return next!=null && next.isKeyEqual(key)?next.val:null;
}
public void remove(K key){
if (containKey(key)){
size--;
int level = maxLevel;
SkipListNode<K,V> pre = head;
while (level >= 0){
pre = mostRightLessNodeInLevel(key,pre,level);
SkipListNode<K,V> next = pre.nextNodes.get(level);
if (next !=null && next.isKeyEqual(key)){
pre.nextNodes.set(level,next.nextNodes.get(level));
}
if (level!=0&& pre == head && pre.nextNodes.get(level)==null){
head.nextNodes.remove(level);
maxLevel--;
}
level--;
}
}
}
public K firstKey(){
return head.nextNodes.get(0) != null ? head.nextNodes.get(0).key : null;
}
public K lastKey(){
int level = maxLevel;
SkipListNode<K,V> cur = head;
while (level >= 0){
SkipListNode<K,V> next = cur.nextNodes.get(level);
while (next != null){
cur = next;
next = cur.nextNodes.get(level);
}
level--;
}
return cur.key;
}
public K ceillingKey(K key){
if (key == null){
return null;
}
SkipListNode<K,V> less = mostRightLessNodeInTree(key);
SkipListNode<K,V> next = less.nextNodes.get(0);
return next != null ? next.key:null;
}
public K floorKey(K key){
if (key == null){
return null;
}
SkipListNode<K,V> less = mostRightLessNodeInTree(key);
SkipListNode<K,V> next = less.nextNodes.get(0);
return next!= null && next.isKeyEqual(key)?next.key:less.key;
}
public int size(){
return size;
}
}
public static void printAll(SkipListMap<String,String> obj){
for (int i = obj.maxLevel;i>=0;i--){
System.out.print("Level "+i+" : ");
SkipListNode<String,String> cur = obj.head;
while (cur.nextNodes.get(i) != null){
SkipListNode<String,String> next = cur.nextNodes.get(i);
System.out.print("("+next.key+" , "+next.val+")");
cur = next;
}
System.out.println();
}
}
public static void main(String[] args) {
SkipListMap<String,String> test = new SkipListMap<>();
printAll(test);
System.out.println("================");
test.put("A","10");
printAll(test);
System.out.println("================");
test.remove("A");
printAll(test);
System.out.println("================");
test.put("E","E");
test.put("B","B");
test.put("A","A");
test.put("F","F");
test.put("C","C");
test.put("D","D");
printAll(test);
System.out.println("================");
System.out.println(test.containKey("B"));
System.out.println(test.containKey("Z"));
System.out.println(test.firstKey());
System.out.println(test.lastKey());
System.out.println(test.floorKey("D"));
System.out.println(test.ceillingKey("D"));
System.out.println("================");
test.remove("D");
printAll(test);
System.out.println("================");
System.out.println(test.floorKey("D"));
System.out.println(test.ceillingKey("D"));
}
}