• redis跳跃表与二分查找


    一 前言

    本篇内容主要是讲解redis跳跃表的基础概念,科普一下读者知道有这种随机数据结构的概念,。

    公众号:知识追寻者

    知识追寻者(Inheriting the spirit of open source, Spreading technology knowledge;)

    二 跳跃表

    2.1 分查找的思想

    说起跳跃表,我们先来回忆一下 二分查找, 这将有助于我们更加容易理解 跳跃表;

    一串有序数组如下 , 我们现在想要 以较快的速度查找出该数组的中的125;

    1 , 2 , 6, 25 , 32 , 48 , 56 ,73 , 85 , 96, 125 ,135 , 148

    首先 125 比中位数56大,向右查找; 剩余如下

    .......,73 , 85 , 96, 125 ,135,148

    其次 125 比 96 大,向右查找;

    ...........125 ,135,1148

    125 比 135 小, 往左 查找;最终结果125; 故经过4 次查找后就找到了本次有序数组中的值; 其时间复杂度未O(logN) ; 如果一个正常的数组进行查找,需要逐个比较,其时间复杂度为 O(N); 明显 二分查找比普通的数组查找快很多;

    其java代码实现如下

    /**
     * @Author lsc
     * <p> </p>
     */
    public class BinaryaFind {
    
        public static void main(String[] args) {
            BinaryaFind binaryaFind = new BinaryaFind();
            int[] array = {1 , 2 , 6, 25 , 32 , 48 , 56 ,73 , 85 , 96, 125 ,135 , 148};
            int result = binaryaFind.binarySearch(array, 125, 0, array.length - 1);
            // result = 10;  array[result] = 125
            System.out.println(array[result]);
        }
    
         /* *
         * @Author lsc
         * <p>递归实现二分查找 </p>
         * @Param [array, target, start, end]
         */
        private int binarySearch(int[] array, int target, int start, int end) {
            if (start > end) {
                return -1;
            }
            int mid = start + (end - start) / 2;
            if (array[mid] == target) {
                return mid;
            } else if (target < array[mid]) {
                return binarySearch(array, target, start, mid - 1);
            } else {
                return binarySearch(array, target, mid + 1, end);
            }
        }
    }
    

    2.2 跳跃表的概念

    跳跃表(skiplist)是一种随机化的数据结构,William Pugh 在论文《Skip lists: a probabilistic alternative to balanced trees》中提出, 跳跃表以有序的方式在层次化的链表中保存元素; 在redis 中的主要应该为zset有序集合的底层实现;

    zskiplist结构的定义如下, 其是跳跃表

    typedef struct zskiplist {
    
        // 表头节点和表尾节点
        struct zskiplistNode *header, *tail;
        // 表中节点的数量
        unsigned long length;
        // 表中层数最大的节点的层数
        int level;
    
    } zskiplist;
    

    redis.h/zskiplistNode 结构定义如下,其是跳跃表节点

    typedef struct zskiplistNode {
    
        // 后退指针
        struct zskiplistNode *backward;
        // 分值
        double score;
        // 成员对象
        robj *obj;
        // 层
        struct zskiplistLevel {
            // 前进指针
            struct zskiplistNode *forward;
            // 跨度
            unsigned int span;
        } level[];
    
    } zskiplistNode;
    

    先不管这段代码你是否读懂,现在我们列出比较重要的概念

    • header:指向跳跃表的表头节点,维护跳跃表节点指针,最高层级为32层
    • tail:指向跳跃表的表尾节点,尾节点全部由null组成
    • level:记录目前跳跃表最大层级;查找时总是由高层往低层级进行查找;
    • length:记录跳跃表的长度
    • zskiplistNode: 节点,保存跳跃表数据信息,前进和后退指针;

    其次再看下下图

    如果进行查找 member = z , score = 5 , 那么 其查找过程为 从表头 到member = x 的L5 层, spand(跨度) = 1;然后 从 member = x 的 L3 层 到 merber =y 的L3 层; 最后从 merber =y 的L3 层 到 merber =z 的L2层; 跨度代表了2个层级之间的距离,跨度越大,距离越远;

    当数据量很大时,通过跳跃表,可以直接通过层级跳跃的方式, 进行查找,有可能 member = x 的 leve l=5,

    member = y 的 level =3 ; merber =z 的 level =5 , 此时直接进行查找就只需要通过一次L5 到L5找就可以找到 member = z 的 score; 固总体来说 其查找的时间复杂度为O(logN); heard , 和 tail 直接可以通过表头,表尾定位得到,其时间复杂度为 O(1);

    关于插入和删除,也是建立在查找的基础上,固其事件复杂度平均也为平均 O(logN);

    2.3 跳跃表API时间复杂度

    • zslCreateNode 创建并返回一个新的跳跃表节点 最坏 O(1)
    • zslFreeNode 释放给定的跳跃表节点 最坏 O(1)
    • zslCreate 创建并初始化一个新的跳跃表 最坏 O(1)
    • zslFree 释放给定的跳跃表 最坏 O(N)
    • zslInsert 将一个包含给定 score 和 member 的新节点添加到跳跃表中 最坏 O(N) 平均 O(logN)
    • zslDeleteNode 删除给定的跳跃表节点 最坏 O(N)
    • zslDelete 删除匹配给定 member 和 score 的元素 最坏 O(N) 平均 O(logN)
    • zslFirstInRange 找到跳跃表中第一个符合给定范围的元素 最坏 O(N) 平均 O(logN)
    • zslLastInRange 找到跳跃表中最后一个符合给定范围的元素 最坏 O(N) 平均 O(logN)
    • zslDeleteRangeByScore 删除 score 值在给定范围内的所有节点 最坏 O(N2)
    • zslDeleteRangeByRank 删除给定排序范围内的所有节点 最坏 O(N2)
    • zslGetRank 返回目标元素在有序集中的排位 最坏 O(N) 平均 O(logN)
    • zslGetElementByRank 根据给定排位,返回该排位上的元素节点 最坏 O(N) 平均 O(logN)

    2.4 选择跳跃表的理由

    总体来说,其实现方式没有红黑数那么复杂,算法速度较快,平均时间复杂度为O(logN);

    三 参考文档

    https://blog.csdn.net/universe_ant/article/details/51134020

    https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html

    《redis设计与实现》

  • 相关阅读:
    一个例子看懂所有nodejs的官方网络demo
    HDU 4283 You Are the One ★(进出栈的括号匹配性质:区间DP)
    HDU 4283 You Are the One ★(进出栈的括号匹配性质:区间DP)
    HDU 4274 Spy's Work (树形DP)
    HDU 4274 Spy's Work (树形DP)
    HUST 1328 String (字符串前缀子串个数 --- KMP)
    HUST 1328 String (字符串前缀子串个数 --- KMP)
    POJ 3167 Cow Pattern ★(KMP好题)
    POJ 3167 Cow Pattern ★(KMP好题)
    HDU 2594 Simpsons’ Hidden Talents (KMP)
  • 原文地址:https://www.cnblogs.com/zszxz/p/13020235.html
Copyright © 2020-2023  润新知