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

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+树。跳表的查询操作在某些情况下可能更迅速。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC-入门

1.概念 SpringMVC是一种软件架构思想&#xff0c;把软件按照模型(Model)、视图(View)、控制器(Controller)这三层来划分。Model&#xff1a;指的是工程中JavaBean&#xff0c;用来处理数据View&#xff1a;指的是工程中的html、jsp等页面&#xff0c;用来展示给用户数据Control…

STM32物联网(ESP-01S模块及STM32和ESP-01S通信方式介绍)

文章目录 前言一、ESP-01S模块介绍二、STM32和ESP-01S通信方式介绍三、什么是AT指令四、创建基础工程总结 前言 本篇文章我们开始正式进入STM32物联网的专栏&#xff0c;在这个专栏中将会带大家学习使用STM32进行联网&#xff0c;联网模块的话主要就是使用到了ESP-01S WIFI模块…

在JavaScript中的防抖函数 - 通过在React中构建自动完成功能来解释

当你将一个新应用推向生产环境时&#xff0c;你希望确保它用户友好。网站的性能是用户体验的关键部分。每个用户都希望网站及其内容能够快速加载。每一秒都是宝贵的&#xff0c;可能导致用户再也不会访问你的网站。 在本指南中&#xff0c;我们将了解JavaScript中一个非常重要…

免费chatgpt使用

基本功能如下&#xff1a; https://go.aigcplus.cc/auth/register?inviteCode3HCULH2UD

【机器学习笔记】3 逻辑回归

分类问题 分类问题监督学习最主要的类型&#xff0c;主要特征是标签离散&#xff0c;逻辑回归是解决分类问题的常见算法&#xff0c;输入变量可以是离散的也可以是连续的 二分类 先从用蓝色圆形数据定义为类型1&#xff0c;其余数据为类型2&#xff1b;只需要分类1次&#x…

蓝桥杯第九届电子类单片机组程序设计(模拟题)

目录 蓝桥杯大赛历届真题 一、第九届比赛题 二、代码实现 main.c iic.c iic.h 前言 蓝桥杯的真题可以再官网上查到&#xff0c;链接放下边了&#xff0c;点击即可跳转到官网&#xff1a; 蓝桥杯大赛历届真题 突然发现官网上的题也不全&#xff0c;而且还有一部分是模拟…

如何合理规划 PostgreSQL 的数据库用户

PostgreSQL 作为世界上最领先的开源数据库&#xff0c;有一套强大的用户角色权限系统&#xff0c;和 MySQL 做一个对比&#xff1a; 但硬币的另一面则是对于简单场景来说增加了复杂度。在许多单应用场景&#xff0c;其实也不需要额外的 schema 层&#xff0c;也不需要额外的 ow…

变量与运算符

目录 1. 关键字&#xff08;keyword&#xff09; 2. 标识符( identifier) 3. 变量 3.1 为什么需要变量 3.2 初识变量 3.3 Java中变量的数据类型 3.4 变量的使用 3.4.1 步骤1&#xff1a;变量的声明 3.4.2 步骤2&#xff1a;变量的赋值 4. 基本数据类型介绍 4.1 整数…

第23讲 微信用户管理实现

package com.java1234.entity;import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data;import java.io.Serializable; import java.util.Date;/*** 微信用户信息实体* author java1234_小…

CPU-GPU异构并行化APSP算法

一、Floyd-Warshall算法 介绍 Floyd-Warshall算法&#xff08;英语&#xff1a;Floyd-Warshall algorithm&#xff09;&#xff0c;中文亦称弗洛伊德算法或佛洛依德算法&#xff0c;是解决任意两点间的最短路径的一种算法&#xff0c;可以正确处理有向图或负权&#xff08;但…

Github用人工智能(AI)帮你的代码修正安全漏洞

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

2024.02.13作业

21. c 22. b 23. b 5先出栈意味着1234都在栈内&#xff0c;此时1不能比2&#xff0c;3先出栈 24. b, c, d: 10, 12, 120 25. 2, 5 26. 数组越界&#xff0c;可能出现段错误 27. 0, 41 28. 1, 320 29. *a *b; *b *a - *b; *a - *b; 30. 0x801005&#xff1b;0x8…

GPT-4带来的思想火花

GPT-4能够以其强大的生成能力和广泛的知识储备激发出众多思想火花。它能够在不同的情境下生成新颖的观点、独特的见解和富有创意的解决方案&#xff0c;这不仅有助于用户突破思维定势&#xff0c;还能促进知识与信息在不同领域的交叉融合。 对于研究者而言&#xff0c;GPT-4可能…

STM32—DHT11温湿度传感器

文章目录 一.温湿度原理1.1 时序图 二.代码 一.温湿度原理 1.1 时序图 (1).下图一是DHT11总的时序图。 (2).图二对应图一的左边黑色部分&#xff0c;图三对应图一的绿色部分&#xff0c;图四的左部分图对应图一的红色部分&#xff0c;图四的右部分对应图一的黄色部分。 (3)…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Navigation组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Navigation组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Navigation组件 鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#…

嵌入式CAN通信协议原理(下)

本篇文章结合实际CAN控制器继续介绍协议相关的内容&#xff0c;还有示例讲解。 好了&#xff0c;继续吧&#xff01; 二. STM32 CAN 控制器介绍 STM32 的芯片中具有 bxCAN 控制器 (Basic Extended CAN)&#xff0c;它支持 CAN 协议 2.0A 和 2.0B 标准。 该 CAN 控制器支持最…

【C语言】socketpair 的系统调用

一、 Linux 内核 4.19socketpair 的系统调用 SYSCALL_DEFINE4(socketpair, int, family, int, type, int, protocol,int __user *, usockvec) {return __sys_socketpair(family, type, protocol, usockvec); } 这段代码定义了一个名为 socketpair 的系统调用。系统调用是操作…

JavaScript基础第三天

JavaScript 基础第三天 今天我们学习for循环、while循环、终止循环和无限循环。 1. for 循环 1.1. 语法 // 1. 语法格式 // for(起始值; 结束条件; 累加器) { // // 要重复执行的代码 // }1.2. 示例代码 let sum 0; for (let i 0; i < 100; i) {sum i; } alert(&q…

阿里云服务器4核16G配置报价和CPU内存性能参数表

阿里云4核16G服务器优惠价格ECS云服务器经济型e实例26元1个月、149元半年、79元3个月&#xff0c;4核16G通用算力u1服务器、通用型g7、通用型g8i、AMD通用型g8a、性能增强通用型g8ae、高主频通用型hfg8i、AMD通用型g7a、内存型r7p等均提供4核16G配置。阿里云服务器网aliyunfuwu…

C#通过重写虚方法实现加、减、乘、除运算 通过多态确定人类的说话行为

目录 一、涉及到的知识点1 1.虚方法 2.重写方法 3.重写方法与重载方法的区别 4.通过KeyPressEventArgs.KeyChar限制键盘输入的内容 5.if-else if-else嵌套转换为switch case 二、 涉及到的知识点2 1.多态性 2.使用多态性的注意事项 3. 使用虚方法实现多态性 三、实…