BST有缺陷--红黑树(RBT)应运而生

news2025/1/13 15:49:22

1.首先介绍一下什么是BST(二叉查找树)

  • 若其左子树非空,则左子树上所有节点的值都小于根节点的值
  • 若其右子树非空,则右子树上所有节点的值都大于根节点的值
  • 其左右子树都是一棵二叉查找树
  • 二叉排序树通过中序遍历可以得到递增序列

 如下:

 把二叉查找树投影下来(中序遍历)就是从小到大排序的数组:1 2 3 4 6 7 9。

那么为什么说BST不行呢?

一个数据结构好不好,主要看它的增删改查的效率。

先来看看BST的查找效率:找上图中的7。

1. 7 > 3 看右子树

2. 7 > 6 看右子树

3. 7 < 9 看左子树

4. 7 = 7 找到了

这种方式正是二分查找的思想,查找所需的最大次数=二叉查找的高度。

删除和修改本质上也是查找,需要 O(logn) 时间。


再来看看BST的插入:

1.假设初始的二叉查找树只有三个节点,根节点值为9,左孩子值为8,右孩子值为12:

2.接下来我们依次插入如下五个节点:7,6,5,4,3。依照二叉查找树的特性,结果会变成什么样呢?

笑死了,这效率和线性的没区别!而这种情况正是BST的缺点。


 2.让我们欢迎本文的主角红黑树!

红黑树是一种自平衡的BST,它是由 Rudolf Bayer 于1978年发明,除了符合BST的基本特性外,还具有以下的特性:

1.节点是红色或黑色。

2.根节点是黑色。

3.每个叶子节点都是黑色的空节点(NIL节点)。

4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

5.从任一节点到其每个叶子(NIL节点)的所有路径都包含相同数目的黑色节点。(区分是否是红黑树的重点)

规则很多,正是因为规则的限定,才保证了红黑树的自平衡。红黑树从根到叶的最长路径不会超过最短路径的2倍。

注意:有些文章写的是NULL节点,是不对的,NIL与NULL有区别!

一、概念不同

Nil:表示无值,任何变量在没有被赋值之前的值都为nil。

Null:是在计算中具有保留的值。

二、功能不同

Nil:用于区别其他任何值。

Null:用于指示指针不引用有效对象。

三、针对不同

Nil:针对对象,而空对象不是说不占用空间,相当于一个“洗白”,回到初始状态。

Null:针对指针,对对象指针和非对象指针都有效,Null不会占用空间。

如下:

 红黑树主要是处理BST的缺点,不过它所有的操作都有点复杂,但是为了效率没办法。

当红黑树执行插入或者删除操作的时候,红黑树的规则可能被打破,这时候就需要做出一些调整来让红黑树保持平衡。

如下操作:

1.向原红黑树插入值为14的新节点:

由于父节点15是黑色节点,因此这种情况并不会破坏红黑树的规则,无需做任何调整。

2.向原红黑树插入值为21的新节点:

由于父节点22是红色节点,因此这种情况打破了红黑树的规则4(每个红色节点的两个子节点都是黑色),必须进行调整,使之重新符合红黑树的规则。

注意为什么新插入的节点是红色?

如果插入节点是黑色,就一定会违背每条查找线上黑色节点个数一致的规则,插入红色,就可能不需要变色或者旋转,所以待插入节点都是红色。

以上问题正是红黑树解决的重点:

解决方法有两种,变色和旋转,旋转分为左旋转和右旋转。

变色:

红黑树是一种平衡的二叉搜索树,它的节点在存储了普通二叉搜索树节点的值和指向左右子节点的指针外,还增加了表示节点颜色的标记,标记可能是红色或者黑色。在红黑树的操作中,经常会用到节点变色的操作,其原理如下:

1.节点变色的本质是改变节点的颜色标记,将红色节点变为黑色,或将黑色节点变为红色。
2.节点变色的前提条件是该节点与其父节点、子节点的颜色关系满足特定条件,即:

(1)不存在两个相邻的红色节点,也就是红色节点的父节点和子节点都是黑色。

(2)根节点必须为黑色。

3.节点变色有两种情况:

(1)红色节点变为黑色。这种情况通常发生在删除操作中,删除红色节点必须保证其子节点为红色,所以删除红色节点后会把其子节点的颜色改为黑色。

(2)黑色节点变为红色。这种情况通常发生在红黑树的平衡调整操作中,如果节点的父节点为黑色,则改为红色后不会影响红黑树的平衡性质,而更改后的颜色有助于平衡调整中旋转操作的实现。

旋转:

红黑树旋转操作是对树进行结构调整的一种操作,主要有左旋和右旋两种。

左旋:将一个节点向左下方移动,并使其成为一个左子节点,同时其右子节点成为新的父节点。左旋的本质是将当前节点的右子树中的某个节点上移到当前节点度上(即作为当前节点的父节点)。

右旋:将一个节点向右上方移动,并使其成为一个右子节点,同时其左子节点成为新的父节点。右旋的本质是将当前节点的左子树中的某个节点上移到当前节点度上(即作为当前节点的父节点)。

左旋和右旋操作都是为了保持红黑树的平衡性,即保持任何节点的左右子树高度相等或相差不超过1,从而实现快速的查找、插入和删除操作。

具体来说,红黑树和AVL树不同之处在于其左右子树的高度可以相差不超过1,因此旋转是随时发生的。当某个节点的左子树高度大于右子树高度时,需要对该节点进行右旋操作;当某个节点的右子树高度大于左子树高度时,需要对该节点进行左旋操作。这样可以不断地调整树的结构,使其保持平衡,并且不会破坏红黑树的特性。


 以上是文字叙述,比较难懂,我这里给个B站讲的最清楚的视频链接:谢某人er的个人空间_哔哩哔哩_bilibili

这位大哥讲的不错,红黑树重在搞懂原理!


 以下是红黑树的C++实现(包含插入、删除、查找、打印等操作):

#include <iostream>

using namespace std;

// 红黑树节点颜色
enum Color {
    RED,
    BLACK
};

// 红黑树节点结构体
struct RBTNode {
    int key;
    Color color;
    RBTNode *left, *right, *parent;
    RBTNode(int k, Color c, RBTNode *l, RBTNode *r, RBTNode *p) : key(k), color(c), left(l), right(r), parent(p) {}
};

// 红黑树类
class RBTree {
private:
    RBTNode *root;  // 红黑树根节点

    /**
     * 左旋
     * @param x 转轴节点
     */
    void leftRotate(RBTNode *x) {
        RBTNode *y = x->right;
        x->right = y->left;
        if (y->left != NULL)
            y->left->parent = x;
        y->parent = x->parent;
        if (x->parent == NULL)
            root = y;
        else if (x == x->parent->left)
            x->parent->left = y;
        else
            x->parent->right = y;
        y->left = x;
        x->parent = y;
    }

    /**
     * 右旋
     * @param x 转轴节点
     */
    void rightRotate(RBTNode *x) {
        RBTNode *y = x->left;
        x->left = y->right;
        if (y->right != NULL)
            y->right->parent = x;
        y->parent = x->parent;
        if (x->parent == NULL)
            root = y;
        else if (x == x->parent->left)
            x->parent->left = y;
        else
            x->parent->right = y;
        y->right = x;
        x->parent = y;
    }

    /**
     * 红黑树插入调整
     * @param z 新插入的节点
     */
    void insertFixup(RBTNode *z) {
        while (z != root && z->parent->color == RED) {
            if (z->parent == z->parent->parent->left) {
                RBTNode *y = z->parent->parent->right;
                if (y != NULL && y->color == RED) {
                    z->parent->color = BLACK;
                    y->color = BLACK;
                    z->parent->parent->color = RED;
                    z = z->parent->parent;
                } else {
                    if (z == z->parent->right) {
                        z = z->parent;
                        leftRotate(z);
                    }
                    z->parent->color = BLACK;
                    z->parent->parent->color = RED;
                    rightRotate(z->parent->parent);
                }
            } else {
                RBTNode *y = z->parent->parent->left;
                if (y != NULL && y->color == RED) {
                    z->parent->color = BLACK;
                    y->color = BLACK;
                    z->parent->parent->color = RED;
                    z = z->parent->parent;
                } else {
                    if (z == z->parent->left) {
                        z = z->parent;
                        rightRotate(z);
                    }
                    z->parent->color = BLACK;
                    z->parent->parent->color = RED;
                    leftRotate(z->parent->parent);
                }
            }
        }
        root->color = BLACK;
    }

    /**
     * 红黑树删除调整
     * @param x 被删除的节点
     */
    void deleteFixup(RBTNode *x) {
        while (x != root && x->color == BLACK) {
            if (x == x->parent->left) {
                RBTNode *w = x->parent->right;
                if (w->color == RED) {
                    w->color = BLACK;
                    x->parent->color = RED;
                    leftRotate(x->parent);
                    w = x->parent->right;
                }
                if ((w->left == NULL || w->left->color == BLACK) && (w->right == NULL || w->right->color == BLACK)) {
                    w->color = RED;
                    x = x->parent;
                } else {
                    if (w->right == NULL || w->right->color == BLACK) {
                        w->left->color = BLACK;
                        w->color = RED;
                        rightRotate(w);
                        w = x->parent->right;
                    }
                    w->color = x->parent->color;
                    x->parent->color = BLACK;
                    w->right->color = BLACK;
                    leftRotate(x->parent);
                    x = root;
                }
            } else {
                RBTNode *w = x->parent->left;
                if (w->color == RED) {
                    w->color = BLACK;
                    x->parent->color = RED;
                    rightRotate(x->parent);
                    w = x->parent->left;
                }
                if ((w->left == NULL || w->left->color == BLACK) && (w->right == NULL || w->right->color == BLACK)) {
                    w->color = RED;
                    x = x->parent;
                } else {
                    if (w->left == NULL || w->left->color == BLACK) {
                        w->right->color = BLACK;
                        w->color = RED;
                        leftRotate(w);
                        w = x->parent->left;
                    }
                    w->color = x->parent->color;
                    x->parent->color = BLACK;
                    w->left->color = BLACK;
                    rightRotate(x->parent);
                    x = root;
                }
            }
        }
        x->color = BLACK;
    }

    /**
     * 查找最小值节点
     * @param node 起始节点
     * @return 最小值节点
     */
    RBTNode *findMinNode(RBTNode *node) {
        while (node->left != NULL)
            node = node->left;
        return node;
    }

    /**
     * 中序遍历打印红黑树
     * @param node 起始节点
     */
    void inorderPrint(RBTNode *node) {
        if (node != NULL) {
            inorderPrint(node->left);
            cout << node->key << " ";
            inorderPrint(node->right);
        }
    }

public:
    // 构造函数
    RBTree() {
        root = NULL;
    }

    // 插入节点
    void insert(int key) {
        RBTNode *z = new RBTNode(key, RED, NULL, NULL, NULL);
        RBTNode *x = root;
        RBTNode *y = NULL;
        while (x != NULL) {
            y = x;
            if (z->key < x->key)
                x = x->left;
            else if (z->key > x->key)
                x = x->right;
            else {
                // 在红黑树中不允许存在相同关键字的节点
                cout << "Error: key " << key << " already exists in RBTree." << endl;
                return;
            }
        }
        z->parent = y;
        if (y == NULL)
            root = z;  // 红黑树为空
        else if (z->key < y->key)
            y->left = z;
        else
            y->right = z;
        insertFixup(z);
    }

    // 删除节点
    void remove(int key) {
        RBTNode *z = root;
        while (z != NULL) {
            if (z->key == key)
                break;
            else if (key < z->key)
                z = z->left;
            else
                z = z->right;
        }
        if (z == NULL) {
            cout << "Error: key " << key << " does not exist in RBTree." << endl;
            return;
        }
        RBTNode *y = z;
        Color yOriginalColor = y->color;
        RBTNode *x;
        if (z->left == NULL) {
            x = z->right;
            transplant(z, z->right);
        } else if (z->right == NULL) {
            x = z->left;
            transplant(z, z->left);
        } else {
            y = findMinNode(z->right);
            yOriginalColor = y->color;
            x = y->right;
            if (y->parent == z)
                x->parent = y;
            else {
                transplant(y, y->right);
                y->right = z->right;
                y->right->parent = y;
            }
            transplant(z, y);
            y->left = z->left;
            y->left->parent = y;
            y->color = z->color;
        }
        if (yOriginalColor == BLACK)
            deleteFixup(x);
        delete z;
    }

    // 查找节点
    RBTNode *find(int key) {
        RBTNode *node = root;
        while (node != NULL) {
            if (node->key == key)
                return node;
            else if (key < node->key)
                node = node->left;
            else
                node = node->right;
        }
        return NULL;
    }

    // 打印红黑树
    void print() {
        inorderPrint(root);
        cout << endl;
    }

private:
    /**
     * 帮助函数:替换节点
     * @param u 原节点
     * @param v 新节点
     */
    void transplant(RBTNode *u, RBTNode *v) {
        if (u->parent == NULL)
            root = v;
        else if (u == u->parent->left)
            u->parent->left = v;
        else
            u->parent->right = v;
        if (v != NULL)
            v->parent = u->parent;
    }
};

 测试示例:

int main() {
    RBTree T;
    T.insert(41);
    T.insert(38);
    T.insert(31);
    T.insert(12);
    T.insert(19);
    T.insert(8);
    T.insert(60);
    T.insert(51);
    T.insert(65);
    T.print();  // 8 12 19 31 38 41 51 60 65
    T.remove(31);
    T.print();  // 8 12 19 38 41 51 60 65
    return 0;
}

屏幕前的你懂了吗? 

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

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

相关文章

技能树-网络爬虫-selenium

文章目录 前言一、selenium二、selenium 测试用例总结 前言 大家好&#xff0c;我是空空star&#xff0c;本篇给大家分享一下《技能树-网络爬虫-selenium》。 一、selenium Selenium是web自动化测试工具集&#xff0c;爬虫可以利用其实现对页面动态资源的采集&#xff0c;对于…

一位老程序员的忠告:别想着靠技术生存一辈子

注&#xff1a;本文系转载。 笔者目前是自己单干&#xff0c;但此前有多年在从事软件开发工作&#xff0c;回头想想自己&#xff0c;特别想对那些初学JAVA/DOT、NET技术的朋友说点心里话&#xff0c;希望我们的体会多少能给你们一些启发。 一、 在一个地方工作8小时就是“穷”…

Python多线程与多进程教程:全面解析、代码案例与优化技巧

文章目录 引言多线程多线程概述案例1&#xff1a;使用多线程实现并发下载文件案例2&#xff1a;使用多线程处理CPU密集型任务 使用threading模块案例1&#xff1a;自定义线程类并启动线程案例2&#xff1a;使用锁保护共享资源 线程同步与互斥案例&#xff1a;使用锁实现线程安全…

css用法总结

1. 块级元素合并时边框重叠问题的解决方案 设置边框2px 红色 如果不做处理&#xff0c;仅仅添加边框则会是这个样子 设置处理后的样式 代码展示 2. 拥有边框的div , hover时的展示效果 代码展示 3. img 和 文字环绕展示 代码展示 设置左浮动即可 4. text-align: center; 可…

Linux——Samba文件共享服务

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。 个人主页&#xff1a;小李会科技的…

Go语言基础-基础语法

前言&#xff1a; \textcolor{Green}{前言&#xff1a;} 前言&#xff1a; &#x1f49e;这个专栏就专门来记录一下寒假参加的第五期字节跳动训练营 &#x1f49e;从这个专栏里面可以迅速获得Go的知识 本文主要是根据今天所学&#xff08;链接放在了最后&#xff09;总结记录的…

please specify ‘programme‘ in launch.json

故障现象&#xff1a; 在windows下点击F5&#xff0c;以运行vscode代码&#xff0c;在屏幕右下角出现这个错误提醒&#xff1b; 故障原因&#xff1a; 在配置文件&#xff08;settings.json或者launch.json&#xff09;中&#xff0c;缺少“program”这个参数配置&#xff1b…

SpringBoot 如何使用 Actuator 进行应用程序监控

SpringBoot 如何使用 Actuator 进行应用程序监控 在现代的应用程序开发中&#xff0c;应用程序监控是非常重要的&#xff0c;因为它可以帮助我们快速发现和解决问题。Spring Boot Actuator 是 Spring Boot 提供的一个强大的监控和管理工具&#xff0c;它可以帮助我们监控和管理…

0003Java程序设计-SSM+JSP现代家庭教育网站

摘 要 本毕业设计的内容是设计并且实现一个基于java技术的现代家庭教育网站。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;java技术和Tomcat网络信息服务作为应用服务器。现代家庭教育网站的功能已基本实现&#xff0c;主要包括主页、个人中心、会员管理、…

Maven如何创建Maven web项目

1、创建一个新的模块: 1.1 使用骨架点一下&#xff0c;这里 1.2 找到maven-archetype-webapp项目&#xff0c;选中点击&#xff0c;一路next就行。 1.3 删除不必要的maven配置&#xff1a;&#xff08;这里我不需要&#xff0c;针对自己情况而定&#xff09; 可以从name这里开…

figma设计软件专业版教育优惠学生使用edu邮箱免费教程

产品介绍 今天一个买家发了一个链接问是否可以用&#xff0c;本站也是第一次见到&#xff0c;就测试了下可以使用教育优惠后准备分享给大家。本站的大多数教育优惠线报其实都是很多网友买家提供的。 Figma是一款用于数字项目的基于云的设计和原型的设计工具软件。 这样做的目…

Python应用实例(一)外星人入侵(二)

1.添加飞船图像 下面将飞船加入游戏中。为了在屏幕上绘制玩家的飞船&#xff0c;我们将加载一幅图像&#xff0c;再使用Pygame方法blit()绘制它。 为游戏选择素材时&#xff0c;务必要注意许可。最安全、最不费钱的方式是使用Pixabay等网站提供的免费图形&#xff0c;无须授权…

强者游戏-敢来挑战否-Amazon DeepRacer League

Amazon DeepRacer中国峰会总决赛 Amazon DeepRacer 自动驾驶赛车名校邀请赛会在6月27日-28日举办的Amazon DeepRacer中国峰会总决赛时同步启动。勇哥的目标是拿个比较好的名词。大家如果有参加这次活动的可以过来一起搞哦。下面我来具体介绍一下这次峰会&#xff0c;以及比赛的…

PyTorch C++ 前端是 PyTorch 机器学习框架的纯 C++ 接口

使用 PyTorch C 前端 PyTorch C 前端是 PyTorch 机器学习框架的纯 C 接口。 虽然 PyTorch 的主要接口自然是 Python&#xff0c;但此 Python API 位于强大的 C 代码库之上&#xff0c;提供基本的数据结构和功能&#xff0c;例如张量和自动微分。 C 前端公开了纯 C 11 API&…

常见面试题之线程基础知识

1. 线程和进程的区别&#xff1f; 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至CPU&#xff0c;数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。 当一…

DataStructure01|ArrayList和顺序表

ArrayList与顺序表 1.线性表 ​ 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列… ​ 线性表在逻辑上是线性结构&#xff0c;也就说…

产品设计.从用户体验五要素出发,谈如何设计产品

用户调研--产品定位---产品方案---视觉设计 作者 | 渐渐见减减简https://www.zcool.com.cn/article/ZMTEyNDA2NA.html 用户体验五要素是一种产品分析与设计的方法论&#xff0c;帮助我们以正确方式从0到1设计一款产品。 1 战略层 企业做一个产品前&#xff0c;都要明确几个问题…

多目标樽海鞘算法MATLAB实战(附源码)

今天给大家分享多目标樽海鞘算法&#xff0c;主要从算法原理和代码实战展开。需要了解智能算法、机器学习、深度学习和信号处理相关理论的可以后台私信哦&#xff0c;下一期分享的内容就是你想了解的内容。 一、算法原理 上一篇分享的SSA算法能够驱动salps向食物来源靠近&…

【Flutter】包管理(6)Flutter 状态管理 Provider 深入使用指南

文章目录 一、 前言二、 ProxyProvider 的使用三、 处理异步数据四、 性能优化五、 版本信息六、 总结一、 前言 在我们的上一篇文章中,我们介绍了 Flutter 中 Provider 包的基本使用。 在这篇文章中,我们将深入探讨 Provider 的高级使用方法,包括如何使用 ProxyProvider,…

Green板 和Red板和Nv EVM板比较

001 电源开关、 复位、烧写按钮 G&#xff1a;绿板 K3: Reset press RN K1: Brush RECOVERY button K2: Start button Power ON R: 红板 K3: POWER_KEY K2: FORCE_RECOVERY K1: RESET_KEY 002 USB设计 烧写连接器 G: 绿板 J6&#xff1a;TYPE C 烧写连接器 USB0_DP …