工程必学!红黑树从概念到手撕实现,讲透平衡树的 “折中智慧”----《Hello C++ Wrold!》(22)--(C/C++)

工程必学!红黑树从概念到手撕实现,讲透平衡树的 “折中智慧”----《Hello C++ Wrold!》(22)--(C/C++)

文章目录

前言

学完 AVL 树后,你是不是也有过这样的疑惑:明明 AVL 树是 “严格平衡” 的二叉搜索树,查询效率还更高,可为啥 C++ STL 的map/set、Linux 内核里的关键结构,偏偏选红黑树而不用它?难道 “更平衡” 反而成了缺点?

其实答案藏在 “工程取舍” 里 —— 红黑树的精髓,从来不是 “比 AVL 树更平衡”,而是 “在‘查询效率’和‘写入开销’之间找最优解”。它不像 AVL 树那样追求 “极致的矮”,而是用 “近似平衡”(最长路径不超过最短路径 2 倍)的规则,换来了插入删除时更少的旋转、更低的空间开销,刚好适配了工程里 “读写频繁、追求均衡性能” 的大多数场景。

这篇内容不绕弯子,直接从红黑树的 5 条核心性质讲起,拆透 “新节点为啥默认红色”“插入时 3 种情况怎么处理” 这些经典难点,再对比 AVL 树说清 “什么时候该选谁”,最后附上可运行的手撕代码和验证逻辑 —— 不管你是为了面试手撕,还是想搞懂工程里的平衡树选型,这篇都能让你把红黑树的本质嚼透。

红黑树的概念

概念:红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black

红黑树的长相例图

红黑树的性质

1.每个结点不是红色就是黑色

2.根节点是黑色的

3.任何路径没有连续的红节点

4.各路径黑节点的个数相同

5.每个叶子结点都是黑色的(此处的叶子结点指的是那个空结点)
这些性质让红黑树的最长路径长度不超过最短路径长度的2倍

–因为一红后面必会跟一黑,各路径黑节点的个数又是一样的
关于路径:从根节点到空节点算一条路径(默认是这样的)

路径长度这一块的话,空节点不算上,根节点要算上
红黑树增删查改的时间复杂度也是O(logN) --N是节点个数
空树也可以是红黑树

AVL树跟红黑树的比较

AVL树和红黑树的在查找时的性能是同一量级的

但是AVL的平衡的控制在插入和删除时的旋转使用的次数很多

所以红黑树比AVL树好些,但是其实也大差不差

AVL的话大量查找要优于红黑树 但是红黑树在各方面更加均衡一点

红黑树的模拟实现

enumColour{ RED, BLACK };template<classK,classV>structRBTreeNode{ RBTreeNode<K, V>* _left; RBTreeNode<K, V>* _right; RBTreeNode<K, V>* _parent; pair<K, V> _kv; Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}};template<classK,classV>structRBTree{typedef RBTreeNode<K, V> Node;public:boolInsert(const pair<K, V>& kv){if(_root ==nullptr){ _root =newNode(kv); _root->_col = BLACK;returntrue;} Node* parent =nullptr; Node* cur = _root;while(cur){if(cur->_kv.first < kv.first){ parent = cur; cur = cur->_right;}elseif(cur->_kv.first > kv.first){ parent = cur; cur = cur->_left;}else{returnfalse;}} cur =newNode(kv); cur->_col = RED;//节点默认给的是红色if(parent->_kv.first < kv.first){ parent->_right = cur;}else{ parent->_left = cur;} cur->_parent = parent;while(parent && parent->_col == RED){ Node* grandfather = parent->_parent;if(parent == grandfather->_left){ Node* uncle = grandfather->_right;// u存在且为红if(uncle && uncle->_col == RED){// 变色 parent->_col = uncle->_col = BLACK; grandfather->_col = RED;// 继续向上处理 cur = grandfather; parent = cur->_parent;}else// u不存在 或 存在且为黑{if(cur == parent->_left){//图:// g// p// cRotateR(grandfather); parent->_col = BLACK; grandfather->_col = RED;}else{// g// p// cRotateL(parent);RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED;}break;}}else// 也就是parent == grandfather->_right{ Node* uncle = grandfather->_left;// u存在且为红if(uncle && uncle->_col == RED){// 变色 parent->_col = uncle->_col = BLACK; grandfather->_col = RED;// 继续向上处理 cur = grandfather; parent = cur->_parent;}else{if(cur == parent->_right){// g// p// cRotateL(grandfather); grandfather->_col = RED; parent->_col = BLACK;}else{// g// p// cRotateR(parent);RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED;}break;}}} _root->_col = BLACK;//防止变色把根节点也变色了returntrue;}private: Node* _root =nullptr;};
插入新节点的时候默认先设置成红色的哈

设置成黑色的话,违反第四点,比设置成红色违反-第三点的调整难度更大

插入新节点的处理

这个处理的前提是插入的节点要先设置成红色的哈

这个新节点刚开始是cur
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

插入一个节点完成后,要把根节点重新改成黑色–怕处理插入问题时颜色被改了
情况一: cur为红,p为红,g为黑,u存在且为红



解决方法:把p,u改为黑,g改为红,然后把g当作cur,继续向上调整
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑 (cur p g直线)




p为g的左孩子,cur为p的左孩子时,进行右旋,然后让p变黑,g变红

p为g的右孩子,cur为p的右孩子时,则进行左旋,然后让p变黑,g变红

这里的旋转是c p g 进行,没有u"参与"哈–怎么旋转可以看AVL树的,一模一样
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑 (折线 p在最左边)




p为g的左孩子,cur为p的右孩子时,则做左旋

p为g的右孩子,cur为p的左孩子时,则做右旋

这里的旋转是c p g 进行,没有u"参与"哈–怎么旋转可以看AVL树的,一模一样

然后就转换成情况2了

红黑树的验证

也就是验证自己的红黑树写没写对
验证的话最主要是验证有没有连续红节点和每条路径的黑节点个数是不是一样多以及根节点是不是黑色的

不要去单独用最短路径和最长路径的关系是判断是不是红黑树–制约不够的
验证有无连续红节点的时候,检查该节点的孩子颜色太麻烦了,不如检查父亲的颜色
boolCheckColour(Node* root,int blacknum,int benchmark){if(root ==nullptr){if(blacknum != benchmark)returnfalse;returntrue;}if(root->_col == BLACK){++blacknum;}if(root->_col == RED && root->_parent && root->_parent->_col == RED){ cout << root->_kv.first <<"出现连续红色节点"<< endl;returnfalse;}returnCheckColour(root->_left, blacknum, benchmark)&&CheckColour(root->_right, blacknum, benchmark);}boolIsBalance(Node* root){if(root ==nullptr)returntrue;if(root->_col != BLACK){returnfalse;}// 基准值--如果是红黑树的话,每条路径的黑色节点应该有多少int benchmark =0; Node* cur = _root;while(cur){if(cur->_col == BLACK)++benchmark; cur = cur->_left;}returnCheckColour(root,0, benchmark);}

作业部分

关于AVL树和红黑树的区别说法不正确的是(C) A.AVL树和红黑树保证平衡性的方式不同 B.AVL树和红黑树都是平衡树,因此查找的时间复杂度都是O(logN) C.AVL树和红黑树的性质遭到破坏时,都需要进行旋转 //红黑树有时不需要旋转 D.AVL树和红黑树中序遍历都可以得到有序序列,因为它们都是二叉搜索树 

Read more

Python详细安装教程——Python及PyCharm超详细安装教程:新手小白也能轻松搞定!(最新版)

Python详细安装教程——Python及PyCharm超详细安装教程:新手小白也能轻松搞定!(最新版)

Python作为一门简单易学、功能强大的编程语言,近年来在数据分析、人工智能、Web开发等领域广受欢迎。而PyCharm作为一款专业的Python集成开发环境(IDE),提供了强大的代码编辑、调试和项目管理功能,是Python开发者的得力助手。本文将详细介绍如何从零开始安装Python和PyCharm,帮助新手小白快速搭建Python开发环境。 一、安装前准备 在安装Python和PyCharm之前,我们需要做一些准备工作,以确保安装过程顺利进行。 1.检查系统要求 (1)操作系统:Windows 7及以上版本。 如何查看自己的操作系统版本: 按下键盘上的“Windows键 + R”组合键,打开“运行”对话框。 输入winver命令,然后按下“回车”键。弹出的“关于Windows”窗口将显示当前操作系统的详细版本信息,包括版本号、内部版本号和系统构建信息。 此外,也可以鼠标左键单击”此电脑“,然后鼠标单击右键,在打开的对话框中点击”属性“,即可查看此电脑的操作系统版本。 本文将以Windows10专业版为例。 (2)内存:

By Ne0inhk
Python开篇:撬动未来的万能钥匙 —— 从入门到架构的全链路指南

Python开篇:撬动未来的万能钥匙 —— 从入门到架构的全链路指南

Python:撬动未来的万能钥匙——从入门到架构的全链路指南 在技术的星空中,Python 是那颗永不陨落的超新星——它用简洁的语法点燃创造之火,以庞大的生态铺就革新之路。无论你身处哪个领域,这把钥匙正在打开下一个时代的大门。2024 年 TIOBE 指数显示,Python 连续五年稳居编程语言榜首,其开发者社区规模同比增长 42%,成为全球技术变革的核心驱动力。 前言     Python以其简洁优雅的语法和强大的通用性,成为当今最受欢迎的编程语言。本专栏旨在系统性地带你从零基础入门到精通Python核心。无论你是零基础小白还是希望进阶的专业开发者,都将通过清晰的讲解、丰富的实例和实战项目,逐步掌握语法基础、核心数据结构、函数与模块、面向对象编程、文件处理、主流库应用(如数据分析、Web开发、自动化)以及面向对象高级特性,最终具备独立开发能力和解决复杂问题的思维,高效应对数据分析、人工智能、Web应用、自动化脚本等广泛领域的实际需求。 🥇 点击进入Python入门专栏,Python凭借简洁易读的语法,是零基础学习编程的理想选择。本专栏专为初学者设计,系统讲解Python核

By Ne0inhk
深入理解 Python HTTP 请求:从基础到高级实战指南

深入理解 Python HTTP 请求:从基础到高级实战指南

目录 * 深入理解 Python HTTP 请求:从基础到高级实战指南 * 章节1:HTTP 协议基础与 Python 生态概览 * HTTP 的核心概念 * Python HTTP 库生态 * 章节2:Requests 库实战:从简单的 GET 到复杂的 API 交互 * 2.1 发送 GET 请求与参数处理 * 2.2 处理 POST 请求与数据提交 * 2.3 必不可少的 Headers 与 Session * 章节3:高级话题:异常处理、超时控制与性能优化 * 3.1 异常处理 (Error Handling) * 3.

By Ne0inhk
全网最全!Python、PyTorch、CUDA 与显卡版本对应关系速查表

全网最全!Python、PyTorch、CUDA 与显卡版本对应关系速查表

摘要:搞深度学习,最痛苦的不是写代码,而是配环境! “为什么我的 PyTorch 认不出显卡?” “新买的显卡装了旧版 CUDA 为什么报错?” 本文提供一份保姆级的版本对应关系速查表,涵盖从 RTX 50 系列 (Blackwell) 到经典老卡的软硬件兼容信息。建议收藏保存,每次配环境前查一下,能省下大量的排坑时间! 🗺️ 核心逻辑图解 在看表格前,先理清显卡架构的代际关系与 CUDA 版本的强绑定逻辑。 📊 一、PyTorch 版本对照表 (推荐) PyTorch 是目前兼容性最好的框架,只要 CUDA 驱动版本 足高,通常都能向下兼容。对于使用最新硬件(如 RTX 50 系)的用户,请务必使用 2.4 或更高版本。 PyTorch 版本Python 版本推荐 CUDA适用显卡建议2.

By Ne0inhk