看了Redis源码还不懂这个数据结构,白看了,附自己的实现代码

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));
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值