https://baijiahao.baidu.com/s?id=1633338040568845450&wfr=spider&for=pc
因为这个周末加班,一直没有更新,实属抱歉,今天,我们来聊一聊一个数据结构,跳表。跳表是redis的一个核心组件,也同时被广泛地运用到了各种缓存地实现当中,它的主要优点,就是可以跟红黑树、AVL等平衡树一样,做到比较稳定地插入、查询与删除。理论插入查询删除的算法时间复杂度为O(logN)。
什么是跳表
链表,相信大家都不陌生,维护一个有序的链表是一件非常简单的事情,我们都知道,在一个有序的链表里面,查询跟插入的算法复杂度都是O(n)。
我们能不能进行优化呢,比如我们一次比较两个呢?那样不就可以把时间缩小一半?
同理,如果我们4个4个比,那不就更快了?
跳表就是这样的一种数据结构,结点是跳过一部分的,从而加快了查询的速度。跳表跟红黑树又有什么差别呢?既然两者的算法复杂度差不多,为什么Redis要使用跳表而不使用红黑树呢?跳表相对于红黑树,主要有这几个优点:1.代码相对简单,手写个跳表还有可能,手写个红黑树试试?
2.如果我们要查询一个区间里面的值,用平衡树可能会麻烦。这里的麻烦指的是实现和理解上,平衡二叉树查询一段区间也是可以做到的。3.删除一段区间,这个如果是平衡二叉树,就会相当困难,毕竟设计到树的平衡问题,而跳表则没有这种烦恼。好了,相信你对跳表已经有一些认识了,我们来简单介绍平衡二叉树的几个基本操作。
查询
假如我们要查询11,那么我们从最上层出发,发现下一个是5,再下一个是13,已经大于11,所以进入下一层,下一层的一个是9,查找下一个,下一个又是13,再次进入下一层。最终找到11。
是不是非常的简单?我们可以把查找的过程总结为一条二元表达式(下一个是否大于结果?下一个:下一层)。理解跳表的查询过程非常重要,试试看查询其他数字,只要你理解了查询,后面两种都非常简单。
插入
插入的时候,首先要进行查询,然后从最底层开始,插入被插入的元素。然后看看从下而上,是否需要逐层插入。可是到底要不要插入上一层呢?我们都知道,我们想每层的跳跃都非常高效,越是平衡就越好(第一层1级跳,第二层2级跳,第3层4级跳,第4层8级跳)。但是用算法实现起来,确实非常地复杂的,并且要严格地按照2地指数次幂,我们还要对原有地结构进行调整。所以跳表的思路是抛硬币,听天由命,产生一个随机数,50%概率再向上扩展,否则就结束。这样子,每一个元素能够有X层的概率为0.5^(X-1)次方。反过来,第X层有多少个元素的数学期望大家也可以算一下。
删除
同插入一样,删除也是先查找,查找到了之后,再从下往上逐个删除。比较简单,就不再赘叙。
总结
跳表,用了计算机中一场非常用的解决问题的思路,随机。随机在深度学习与人工智能领域运用得非常的广泛。(不仅人会蒙,计算机也是很会蒙的)今天的介绍我们就讲到这里,如果你有兴趣,欢迎关注我,最近准备了一些AI相关的,苦于时间有限,整理后后面会和大家继续分享。
************************************************************************************************************************************************
以上是从别的地方复制过来的,对于查询的话,是很容易理解的,基本类似二分查找,二分查找的效率也是非常高的,查找优化的方式,也是尽力往二分查找上去靠拢。最近突然明白数据结构中树是用来干什么的了,树:搜索,查找,堆(平衡二叉树):排序,经典题目:查找数组中前K大或者前K小的元素。但是对于跳表的插入过程,说的特别隐晦,反正我是没看懂,下面是我找的跳表查找,插入的代码,加上了自己理解的备注,供有缘人参考吧。
插入:第一层找到待插入的位置(curNode的节点next),插入完成后,随机函数判断插入的新节点是否要成为上层的一个索引节点,需要的话,curNode节点往前走,走到一个待节点Up指针不为空的,走到上一层的curNode1,然后把节点插入的curNode1后边,然后继续往上一层找……,是否插入上一层,都是随机函数确定的,所以每次执行程序,跳表的层数和结构都不同。
删除:删除没有对应的实现方式,我觉得删除的过程,先删除第一层的数据,然后,网上找,依次删除上边每一层的数据。
以下是具体的跳表查找和插入的代码,供参考
package org.fuyu.algorithm.list;
import java.util.Arrays;
import java.util.Random;
/**
* 跳表
*/
public class SkipList<K extends Comparable<K>, V> {
private Node<K, V> head;//k,v 都是 NULL
private Integer levels = 0;
private Integer length = 0;
private Random random = new Random(System.currentTimeMillis());
public SkipList() {
createNewLevel();
}
private Node<K, V> findNode(K key) {
Node<K, V> curNode = this.head;
for (; ; ) {
//curNode.next.key <= key, 继续往后找
while (curNode.getNext() != null && curNode.getNext().getKey().compareTo(key) <= 0) {
curNode = curNode.getNext();
}
//走到下一层,直到第1层且curNode.next.key >= key为止
if (curNode.getDown() != null) {
curNode = curNode.getDown();
} else {
break;
}
}
return curNode;
}
public V get(K key) {
Node<K, V> node = findNode(key);
if (key.equals(node.getKey())) {
return node.getValue();
}
return null;
}
public void put(K key, V value) {
if (key == null || value == null) {
return;
}
Node<K, V> newNode = new Node<>(key, value);
insertNode(newNode);
}
private void insertNode(Node<K, V> newNode) {
Node<K, V> curNode = findNode(newNode.getKey());
//相等则更新,否则插入到下一个节点
if (curNode.getKey() == null) {
insertNext(curNode, newNode);
} else if (curNode.getKey().compareTo(newNode.getKey()) == 0) { //update
curNode.setValue(newNode.getValue());
return;
} else {
insertNext(curNode, newNode);
}
int currentLevel = 1;
Node<K, V> oldTop = newNode;
while (random.nextInt(100) < 50) {
Node<K, V> newTop = new Node<>(newNode.getKey(), null);
if (currentLevel >= levels) {
createNewLevel();
}
//往前找到第一个 up != null
while (curNode.getPre() != null && curNode.getUp() == null) {
curNode = curNode.getPre();
}
if (curNode.getUp() == null) {
continue;
}
//到了上一层
curNode = curNode.getUp();
//curNode->newTop,newTop插入curNode后
Node<K, V> curNodeNext = curNode.getNext();
if(curNodeNext != null){
curNodeNext.setPre(newTop);
}
newTop.setNext(curNodeNext);
curNode.setNext(newTop);
newTop.setPre(curNode);
//newTop->oldTop, newTop插入oldTop上面
newTop.setDown(oldTop);
oldTop.setUp(newTop);
//继续往上层找
oldTop = newTop;
currentLevel++;
}
}
private void createNewLevel() {
Node<K, V> newHead = new Node<>(null, null);
if (this.head == null) {
this.head = newHead;
this.levels++;
return;
}
this.head.setUp(newHead);
newHead.setDown(this.head);
this.head = newHead;
this.levels++;
}
private void insertNext(Node<K, V> curNode, Node<K, V> newNode) {
Node<K, V> curNodeNext = curNode.getNext();
newNode.setNext(curNodeNext);
if (curNodeNext != null) {
curNodeNext.setPre(newNode);
}
curNode.setNext(newNode);
newNode.setPre(curNode);
this.length++;
}
public void print() {
Node<K, V> curI = this.head;
//length + 1:每层有一个头结点,
String[][] strings = new String[levels][length + 1];
for (String[] string : strings) {
//二维数组,所有元素默认全是0
Arrays.fill(string, "0");
}
// 走到第一层的第一个
while (curI.getDown() != null) {
curI = curI.getDown();
}
System.out.println("levels:" + levels + "_" + "length:" + length);
//外层循环→:遍历第一层,内层循环↑:往上找每一列 ↑
int i = 0;
while (curI != null) {
Node<K, V> curJ = curI;
int j = levels - 1;
while (curJ != null) {
//从下往上,写入对应的数值
strings[j][i] = String.valueOf(curJ.getKey());
if (curJ.getUp() == null) {
break;
}
//内层循环↑
curJ = curJ.getUp();
j--;
}
if (curI.getNext() == null) {
break;
}
//外层循环 →
curI = curI.getNext();
i++;
}
//遍历输入二维数组的值
for (String[] string : strings) {
System.out.println(Arrays.toString(string));
}
}
static final class Node<K extends Comparable<K>, V> {
private K key;
private V value;
private Node<K, V> up, down, pre, next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public Node<K, V> getUp() {
return up;
}
public void setUp(Node<K, V> up) {
this.up = up;
}
public Node<K, V> getDown() {
return down;
}
public void setDown(Node<K, V> down) {
this.down = down;
}
public Node<K, V> getPre() {
return pre;
}
public void setPre(Node<K, V> pre) {
this.pre = pre;
}
public Node<K, V> getNext() {
return next;
}
public void setNext(Node<K, V> next) {
this.next = next;
}
@Override
public String toString() {
return "Node{" + "key=" + key +
", value=" + value +
", hashcode=" + hashCode() +
", up=" + (up == null ? "null" : up.hashCode()) +
", down=" + (down == null ? "null" : down.hashCode()) + ", pre=" + (pre == null ? "null" : pre.hashCode()) +
", next=" + (next == null ? "null" : next.hashCode()) +
'}';
}
}
}
测试类:
package org.fuyu.algorithm.list;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* 跳表测试类
*/
public class SkipListTest {
/**
* Rigorous Test :-)
*/
@Test
public void skipList() {
SkipList<Integer, String> skipList = new SkipList<>();
skipList.put(2, "B");
skipList.put(1, "A");
skipList.put(3, "C");
skipList.put(4, "D");
skipList.put(5, "E");
skipList.put(6, "F");
skipList.print();
System.out.println(skipList.get(2));
}
}