redis为什么使用跳跃表而不是树

news2024/12/23 22:25:18

Redis中支持五种数据类型中有序集合Sorted Set的底层数据结构使用的跳跃表,为何不使用其他的如平衡二叉树、b+树等数据结构呢?

1,redis的设计目标、性能需求:

redis是高性能的非关系型(NoSQL)内存键值数据库,它以其快速的操作速度而闻名。
读取速度:Redis能实现极高的读取速度,据官方测试报告,可以达到每秒约110,000次读取操作。
写入速度:与读取相比,写入速度略低,但仍然相当可观,官方数据显示,Redis的写入速度大约是每秒81,000次操作。

类似产品如Memcached等,无法达到如此性能。

2,有序集合都可以借助什么数据结构及其基本原理

有序集合需求:自然有序,查找高速,支持范围查找

2.1,传统数组/链表+排序

数组或链表可以存储数据,可以新增或修改数据后重新排序,

而在集合排序方面,最快的归并排序也需要O(NlongN)的时间复杂度。
时间不够快,但实现、使用方面简单
在这里插入图片描述

2.2,跳跃表(链表的优化–链表+多级索引)

跳跃表最早是由William Pugh在1990年提出的,被用来代替平衡树(如AVL树和红黑树)来实现有序集合。跳跃表的查询复杂度为O(log n),与平衡树相当,但由于其实现较为简单,所以在实际使用中比平衡树更加高效。

例:查找90
在这里插入图片描述

增加指针,让指针指向远处个节点,
如上图,共四层,最底层(原链表L1)节点是10 - 20 - 30 -… - 120,中间层L2增加节点10 - 30 - 40 - 60 - 80 - 100 - 120,L2上层L3增加节点10 - 40 - 80 - 120 最高层10 - 120

这样形成三个新的链表,但它包含的节点个数只有原来的一部分
当我们查找数据之时,先沿着这个最高层链表进行查找。当碰到比待查数据大的节点时,再到中间层,最后回到原来的链表中进行查找。

如查找90,共经历6步。

过程类似二分查找,时间复杂度支持平均O(logN),最坏O(N)的节点查找,还可以顺序性操作来批量处理节点。

2.3,平衡二叉树/红黑树

在这里插入图片描述
平衡二叉树的查询复杂度为O(logN),但新增、删除需要调整保持平衡,实现相对复杂;
范围查询方面,平衡二叉树支持范围查询,但是这这种数据结构要范围查找要往回找,即回溯到父结点,而B+树的叶子结点的指针的效率则更高

2.4,B+树

B+ Tree是一种经典的多路平衡查找树,它通常被用来实现磁盘上的数据结构。在B+ Tree中,每个节点可以包含多个键值对,而且所有叶子节点都被连接成一个链表。B+ Tree的查询复杂度也是O(log n),但由于其实现较为复杂,所以在实际使用中通常用于数据库系统等需要高效读写的场景中。
在这里插入图片描述

3,跳跃表与平衡树的实现

在这里插入图片描述

//redis源码中跳跃表结构的实现:
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    sds ele;
    double score;//分值
    struct zskiplistNode *backward;//后退指针
    //层
    struct zskiplistLevel {
        struct zskiplistNode *forward;//前进指针
        unsigned long span;//跨度
    } level[];
} zskiplistNode;

如图,一个跳表节点,有level数组,每个元素都有一个指向其他节点的指针,可以通过这些层来加快访问速度

3.1使用java实现跳跃表:

import java.util.Random;

class Node {
    int key;
    int value;
    Node[] next;

    public Node(int level, int key, int value) {
        this.key = key;
        this.value = value;
        this.next = new Node[level + 1];
    }
}

public class SkipList {
    private static final int MAX_LEVEL = 16; // 最大层数
    private int level; // 当前层数
    private Node head; // 头节点
    private Random random; // 用于生成随机层数

    public SkipList() {
        this.level = 0;
        this.head = new Node(MAX_LEVEL, 0, 0);
        this.random = new Random();
    }

    // 生成随机层数
    private int randomLevel() {
        int level = 0;
        while (level < MAX_LEVEL && random.nextDouble() < 0.5) {
            level++;
        }
        return level;
    }

    // 插入节点
    public void insert(int key, int value) {
        Node[] update = new Node[MAX_LEVEL + 1];
        Node current = head;

        for (int i = level; i >= 0; i--) {
            while (current.next[i] != null && current.next[i].key < key) {
                current = current.next[i];
            }
            update[i] = current;
        }

        int newLevel = randomLevel();
        if (newLevel > level) {
            for (int i = level + 1; i <= newLevel; i++) {
                update[i] = head;
            }
            level = newLevel;
        }

        Node newNode = new Node(newLevel, key, value);
        for (int i = 0; i <= newLevel; i++) {
            newNode.next[i] = update[i].next[i];
            update[i].next[i] = newNode;
        }
    }

    // 查找节点
    public Node search(int key) {
        Node current = head;
        for (int i = level; i >= 0; i--) {
            while (current.next[i] != null && current.next[i].key < key) {
                current = current.next[i];
            }
        }

        if (current.next[0] != null && current.next[0].key == key) {
            return current.next[0];
        }

        return null;
    }

    // 删除节点
    public void delete(int key) {
        Node[] update = new Node[MAX_LEVEL + 1];
        Node current = head;

        for (int i = level; i >= 0; i--) {
            while (current.next[i] != null && current.next[i].key < key) {
                current = current.next[i];
            }
            update[i] = current;
        }

        if (current.next[0] != null && current.next[0].key == key) {
            for (int i = 0; i <= level; i++) {
                if (update[i].next[i] != current.next[i]) {
                    break;
                }
                update[i].next[i] = current.next[i];
            }

            while (level > 0 && head.next[level] == null) {
                level--;
            }
        }
    }

    // 打印跳跃表
    public void printSkipList() {
        for (int i = level; i >= 0; i--) {
            Node current = head;
            System.out.print("Level " + i + ": ");
            while (current.next[i] != null) {
                System.out.print(current.next[i].key + " ");
                current = current.next[i];
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        SkipList skipList = new SkipList();

        skipList.insert(3, 30);
        skipList.insert(1, 10);
        skipList.insert(2, 20);
        skipList.insert(4, 40);

        System.out.println("Skip List after insertion:");
        skipList.printSkipList();

        int searchKey = 2;
        Node searchResult = skipList.search(searchKey);
        if (searchResult != null) {
            System.out.println("Node with key " + searchKey + " found. Value: " + searchResult.value);
        } else {
            System.out.println("Node with key " + searchKey + " not found.");
        }

        int deleteKey = 2;
        skipList.delete(deleteKey);
        System.out.println("Skip List after deletion of key " + deleteKey + ":");
        skipList.printSkipList();
    }
}

3.2使用java实现平衡二叉树(AVLTree):

class Node {
    int key, height;
    Node left, right;

    public Node(int key) {
        this.key = key;
        this.height = 1;
    }
}

public class AVLTree {
    private Node root;

    // 获取节点的高度
    private int height(Node node) {
        return (node == null) ? 0 : node.height;
    }

    // 获取树的平衡因子
    private int getBalance(Node node) {
        return (node == null) ? 0 : height(node.left) - height(node.right);
    }

    // 更新节点的高度
    private void updateHeight(Node node) {
        node.height = 1 + Math.max(height(node.left), height(node.right));
    }

    // 执行右旋转
    private Node rightRotate(Node y) {
        Node x = y.left;
        Node T2 = x.right;

        // 执行旋转
        x.right = y;
        y.left = T2;

        // 更新高度
        updateHeight(y);
        updateHeight(x);

        return x;
    }

    // 执行左旋转
    private Node leftRotate(Node x) {
        Node y = x.right;
        Node T2 = y.left;

        // 执行旋转
        y.left = x;
        x.right = T2;

        // 更新高度
        updateHeight(x);
        updateHeight(y);

        return y;
    }

    // 插入节点
    public Node insert(Node node, int key) {
        if (node == null) {
            return new Node(key);
        }

        // 执行标准的BST插入
        if (key < node.key) {
            node.left = insert(node.left, key);
        } else if (key > node.key) {
            node.right = insert(node.right, key);
        } else {
            // 相等的键不允许插入
            return node;
        }

        // 更新节点的高度
        updateHeight(node);

        // 获取平衡因子
        int balance = getBalance(node);

        // 进行平衡操作
        // 左重,需要右旋转
        if (balance > 1 && key < node.left.key) {
            return rightRotate(node);
        }
        // 右重,需要左旋转
        if (balance < -1 && key > node.right.key) {
            return leftRotate(node);
        }
        // 左右,先左旋转后右旋转
        if (balance > 1 && key > node.left.key) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }
        // 右左,先右旋转后左旋转
        if (balance < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        // 不需要平衡操作,直接返回节点
        return node;
    }

    // 插入节点的公共接口
    public void insert(int key) {
        root = insert(root, key);
    }

    // 打印中序遍历结果
    private void inOrderTraversal(Node node) {
        if (node != null) {
            inOrderTraversal(node.left);
            System.out.print(node.key + " ");
            inOrderTraversal(node.right);
        }
    }

    // 打印中序遍历结果的公共接口
    public void inOrderTraversal() {
        inOrderTraversal(root);
        System.out.println();
    }

    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();

        // 插入节点
        avlTree.insert(10);
        avlTree.insert(20);
        avlTree.insert(30);
        avlTree.insert(15);
        avlTree.insert(5);

        // 打印中序遍历结果
        System.out.println("Inorder traversal of the AVL tree:");
        avlTree.inOrderTraversal();
    }
}

3.3java实现B+树:

import java.util.ArrayList;
import java.util.List;

class BPlusTree {
    private BPlusNode root;
    private int order;

    public BPlusTree(int order) {
        this.root = new BPlusLeafNode();
        this.order = order;
    }

    public void insert(int key, String value) {
        root = root.insert(key, value);
    }

    public String search(int key) {
        return root.search(key);
    }

    public void printTree() {
        root.print();
    }

    // B+树节点抽象类
    abstract static class BPlusNode {
        List<Integer> keys;

        BPlusNode() {
            this.keys = new ArrayList<>();
        }

        abstract BPlusNode insert(int key, String value);

        abstract String search(int key);

        abstract void print();
    }

    // B+树叶子节点类
    static class BPlusLeafNode extends BPlusNode {
        List<String> values;
        BPlusLeafNode next; // 用于连接叶子节点形成链表

        BPlusLeafNode() {
            this.values = new ArrayList<>();
            this.next = null;
        }

        @Override
        BPlusNode insert(int key, String value) {
            int index = 0;
            while (index < keys.size() && keys.get(index) < key) {
                index++;
            }

            keys.add(index, key);
            values.add(index, value);

            // 检查是否需要分裂
            if (keys.size() > order) {
                int splitIndex = keys.size() / 2;
                BPlusLeafNode newLeaf = new BPlusLeafNode();

                // 将一半的键和值移至新节点
                newLeaf.keys.addAll(keys.subList(splitIndex, keys.size()));
                newLeaf.values.addAll(values.subList(splitIndex, values.size()));
                keys.subList(splitIndex, keys.size()).clear();
                values.subList(splitIndex, values.size()).clear();

                // 调整叶子节点链表
                newLeaf.next = next;
                next = newLeaf;

                return newLeaf;
            }

            return this;
        }

        @Override
        String search(int key) {
            int index = 0;
            while (index < keys.size() && keys.get(index) <= key) {
                if (keys.get(index) == key) {
                    return values.get(index);
                }
                index++;
            }

            return null;
        }

        @Override
        void print() {
            System.out.print("Leaf Node: ");
            for (int i = 0; i < keys.size(); i++) {
                System.out.print("(" + keys.get(i) + ", " + values.get(i) + ") ");
            }
            System.out.println();
        }
    }

    // B+树内部节点类
    static class BPlusInternalNode extends BPlusNode {
        List<BPlusNode> children;

        BPlusInternalNode() {
            this.children = new ArrayList<>();
        }

        @Override
        BPlusNode insert(int key, String value) {
            int index = 0;
            while (index < keys.size() && keys.get(index) < key) {
                index++;
            }

            BPlusNode child = children.get(index);
            BPlusNode newChild = child.insert(key, value);
            if (newChild != child) {
                keys.add(index, newChild.keys.get(0));
                children.add(index + 1, newChild);
                if (keys.size() > order) {
                    int splitIndex = keys.size() / 2;
                    BPlusInternalNode newInternal = new BPlusInternalNode();

                    // 将一半的键和子节点移至新节点
                    newInternal.keys.addAll(keys.subList(splitIndex, keys.size()));
                    newInternal.children.addAll(children.subList(splitIndex + 1, children.size()));
                    keys.subList(splitIndex, keys.size()).clear();
                    children.subList(splitIndex + 1, children.size()).clear();

                    return newInternal;
                }
            }

            return this;
        }

        @Override
        String search(int key) {
            int index = 0;
            while (index < keys.size() && keys.get(index) <= key) {
                index++;
            }

            return children.get(index).search(key);
        }

        @Override
        void print() {
            System.out.print("Internal Node: ");
            for (int i = 0; i < keys.size(); i++) {
                System.out.print(keys.get(i) + " ");
            }
            System.out.println();

            for (BPlusNode child : children) {
                child.print();
            }
        }
    }

    public static void main(String[] args) {
        BPlusTree bPlusTree = new BPlusTree(3);

        bPlusTree.insert(10, "Value10");
        bPlusTree.insert(20, "Value20");
        bPlusTree.insert(5, "Value5");
        bPlusTree.insert(6, "Value6");
        bPlusTree.insert(12, "Value12");
        bPlusTree.insert(30, "Value30");

        System.out.println("B+ Tree after insertion:");
        bPlusTree.printTree();

        int searchKey = 12;
        String searchResult = bPlusTree.search(searchKey);
        if (searchResult != null) {
            System.out.println("Value for key " + searchKey + ": " + searchResult);
        } else {
            System.out.println("Key " + searchKey + " not found.");
        }
    }
}

4,Redis的作者 @antirez 的原话与总结

There are a few reasons:
1、They are not very memory intensive. It’s up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.
2、A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.
3、They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.
主要是从内存占用、对范围查找的支持、实现难易程度这三方面总结的原因,
简单翻译一下:
1、它们不是非常内存密集型的。基本上由你决定。改变关于节点具有给定级别数的概率的参数将使其比 btree 占用更少的内存。
2、Zset 经常需要执行 ZRANGE 或 ZREVRANGE 的命令,即作为链表遍历跳表。通过此操作,跳表的缓存局部性至少与其他类型的平衡树一样好。
3、它们更易于实现、调试等。例如,由于跳表的简单性,我收到了一个补丁(已经在Redis master中),其中扩展了跳表,在 O(log(N) 中实现了 ZRANK。它只需要对代码进行少量修改。

跳跃表的优势:

1,时间复杂度方面:大部分情况下,跳跃表的效率和平衡树媲美;
2,算法实现方面:跳跃表的实现比平衡树、b+树更为简单;
3,范围查找方面,跳表比平衡树操作要简单,平衡树需要回溯到父结点,条表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历;
4,对于小数据集的性能: 在某些场景下,跳表在小数据集上的性能可能优于B+树。跳表的查询操作在某些情况下可能更迅速。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1452978.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在Postgresql 下安装QGIS

安装QGIS的前提是需要 在windows下安装Postgres&#xff0c;具体可以参考文章&#xff1a; Windows 安装和连接使用 PgSql数据库 安装GIS的具体步骤如下&#xff1a; 一.打开 Application Stack Builder 二.选择默认端口和安装目标 三.选择【Spatial Extensions】 四.选择安装…

链式结构实现队列

链式结构实现队列 1.队列1.1队列的概念及结构1.2队列的实现 2. 队列的各种函数实现3. 队列的全部代码实现 1.队列 1.1队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出 FIFO(Fi…

【大厂AI课学习笔记】【2.1 人工智能项目开发规划与目标】(5)数据管理

今天学习了数据管理&#xff0c;以及数据管理和数据治理的区别和联系。 数据管理&#xff1a;利用计算机硬件和软件技术对数据进行有效的收集、存储、处理和应用的过程其目的在于充分有效地发挥数据的作用。 实现数据有效管理的关键是数据组织。 数据管理和数据治理的区别&am…

无人驾驶控制算法LQR和MPC的仿真实现

1. LQR控制器 1.1 问题陈述 考虑一个质量为 m m m 的滑块在光滑的一维地面上运动。初始时&#xff0c;滑块的位置和速度均为 0 0 0。我们的目标是设计一个控制器&#xff0c;基于传感器测得的滑块位置 x x x&#xff0c;为滑块提供外力 u u u&#xff0c;使其能够跟随参考…

每日一题——LeetCode1455.检查单词是否为句中其他单词的前缀

方法一 js函数slice() 将字符串按空格符分割为单词数组&#xff0c;记searchWord的长度为n&#xff0c;分割每个单词的前n位看是否和searchWord匹配 var isPrefixOfWord function(sentence, searchWord) {let res sentence.split(" ")for(i 0 ; i < res.lengt…

七天入门大模型 :大模型LLM 训练理论和实战最强总结!

本文对于想入门大模型、面试大模型岗位、大模型实具有很强的指导意义。喜欢记得收藏、关注、点赞 文章目录 技术交流群用通俗易懂方式讲解系列总览介绍预训练范式如何确定自己的模型需要做什么训练&#xff1f;模型推理的一般过程PyTorch 框架设备PyTorch基本训练代码范例Trans…

【复现】cellinx摄像设备 未授权漏洞_50

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 cellinx是一家韩国的摄像设备 二 .漏洞影响 通过未授权访问可以创建用户进入后台&#xff0c;可能造成系统功能破坏。 三.漏洞复…

CCF编程能力等级认证GESP—C++8级—20231209

CCF编程能力等级认证GESP—C8级—20231209 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)奖品分配大量的工作沟通 答案及解析单选题判断题编程题1编程题2…

GIS利用不舒适指数绘制地区的生物气候舒适度图

生物气候舒适度定义了最适宜的气候条件,在这种条件下,人们感到健康和充满活力。生物气候舒适度地图对城市规划研究特别有用。温度、相对湿度和风速等要素对评估生物气候舒适度非常重要。[1] 人们已经得出了许多不同的指数来确定生物气候舒适度。在本博文中,我们将使用广泛使…

基于SringBoot+Vue的大学生社团管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 1.1 研究背景 1.…

英文论文(sci)解读复现【NO.21】一种基于空间坐标的轻量级目标检测器无人机航空图像的自注意

此前出了目标检测算法改进专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读发表高水平学术期刊中的 SCI论文&a…

leetcode hot100不同路径Ⅱ

本题和之前做的不同路径类似&#xff0c;区别是本题中加入了障碍&#xff0c;遇到障碍之后需要避开&#xff08;注意&#xff0c;这里依旧是只能向下向右移动&#xff09;&#xff0c;那么也就是说&#xff0c;有障碍的点是到达不了的&#xff0c;并且 &#xff0c;我在初始化的…

Java基于微信小程序的医院挂号小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Atmel ATSHA204应用总结

1 ACES软件安装 Atmel Crypto Evaluation Studio (ACES) https://www.microchip.com/DevelopmentTools/ProductDetails/PartNO/Atmel%20Crypto%20%20Studio%20(ACES) 2 基本概念 ACES CE&#xff1a;Atmel Crypto Evalution Studio Configuration Environment&#xff08;基于加…

Intelij Terminal中文乱码解决

第一&#xff1a; &#xff08;重启Intelij生效&#xff09; -Dfile.encodingUTF-8 第二&#xff1a; &#xff08;重启Intelij生效&#xff09; 如果还不行&#xff0c;第三&#xff1a; 测试结果很ok&#xff1a;

红队打靶练习:IMF: 1

目录 信息收集 1、arp 2、nmap 3、nikto 目录探测 gobuster dirsearch WEB 信息收集 get flag1 get flag2 get flag3 SQL注入 漏洞探测 脱库 get flag4 文件上传 反弹shell 提权 get flag5 get flag6 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# a…

使用MinIO S3存储桶备份Weaviate

Weaviate 是一个开创性的开源向量数据库&#xff0c;旨在通过利用机器学习模型来增强语义搜索。与依赖关键字匹配的传统搜索引擎不同&#xff0c;Weaviate 采用语义相似性原则。这种创新方法将各种形式的数据&#xff08;文本、图像等&#xff09;转换为矢量表示形式&#xff0…

怎么在jupyter notebook中运行R

文章目录 需要安装的R包将jupyter和R进行关联修改镜像(缩短包的下载时间)最终效果图 需要安装的R包 repr, IRdisplay, evaluate, crayon, pbdZMQ, devtools, uuid, digest&#xff0c;IRkernel使用命令 install.packages(c(repr, IRdisplay, evaluate, crayon, pbdZMQ, devto…

Javaweb之SpringBootWeb案例之AOP通知类型的详细解析

3.1 通知类型 在入门程序当中&#xff0c;我们已经使用了一种功能最为强大的通知类型&#xff1a;Around环绕通知。 Around("execution(* com.itheima.service.*.*(..))") public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//记录方法执行开始…

自动化测试:电商管理系统元素定位练习​

本次专题我们来说一下 Python中Unittest 框架的使用及如何通过HTMLTestRunner实现自动化测试报告的自动生成。案例中的代码我们仍旧使用课堂学习中部署的“电商管理系统”来实现。本次练习包括以下几个操作&#xff1a; l 测试用例整体结构设计 l 测试用例的实现 l 测试套的…