当前位置: 首页 > news >正文

数据结构之 【红黑树的简介与插入问题的实现】

目录

1.红黑树的概念及规则

2.AVL树与红黑树的性能对比

3红黑树节点定义

4.红黑树的插入

1. 按照二叉搜索树的方式插入新节点

2. 按需变色或旋转+变色

(1)uncle 存在且为红

(2)uncle 不存在

(3)uncle存在且为黑

3.记忆方法

5.验证红黑树


 

部分红黑树图画来自维基百科!!

1.红黑树的概念及规则

红黑树是一种自平衡的二叉搜索树

通过特定规则和旋转操作确保最长路径的节点数不超过最短路径的两倍,因而是接近平衡的

通过以下规则维护近似平衡

1.每个节点非红即黑,根节点和叶子节点(NIL)为黑

这里说的叶子节点、NIL节点就是空节点

2.红色节点的子节点必须为黑

即任意路径上无连续红节点

3.从任意节点到其所有叶子节点的路径上,黑色节点数量相同

简称黑高一致

这些规则保证了最长路径(即红黑交替)的节点数不超过最短路径(即全黑)的两倍

下图为一棵合法的红黑树:

2.AVL树与红黑树的性能对比

N个数据存储在AVL树与红黑树中:

(1)AVL树高约为 1.44 logN,红黑树高约为 2 logN(上界)   (我查询的数据,我也不会推导^_^)  高度差异对性能影响有限(如10亿数据时,AVL树约43层,红黑树约60层,CPU缓存可高效处理)

(2)关键区别:AVL树的苛刻平衡条件导致插入/删除时需大量旋转(最多O(logN)次),而红黑树仅需固定次数的旋转(最多2~3次)。

因此,红黑树在动态操作上效率更高,综合性能更优(可评10分),AVL树在查找密集型场景中略优(9.5分)

两者查找时间复杂度均为 O(logN),实际差异可忽略。

3红黑树节点定义

使用 KV 模型, 此概念可参考 前期博客  二叉搜索树的应用

enum Color
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Color _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){ }
};

(1)节点非黑即红,使用枚举列举两种颜色

RBTreeNode类负责树节点的定义及初始化:

(1)使用模板,以适应不同数据类型

(2)struct定义,将节点暴露方便后续使用

(3这里使用三叉链,即一个节点既保存其左右孩子节点指针又保存其父亲节点指针

(4)节点中再保存存储数据的 _kv 及 颜色表示 _col

4.红黑树的插入

红黑树是特殊的二叉搜索树,红黑树的插入就是在二叉搜索树插入的基础上通过判断节点颜色的互斥性,进而按需变色或旋转,最终达到近似平衡的过程

那么AVL树的插入过程可以分为两步:

1. 按照二叉搜索树的方式插入新节点

template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (!_root){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = _root;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}//控制红黑节点//...//插入结束即是成功return true;}private://可以偷懒在这初始化Node* _root = nullptr;
};

前半截插入过程与二叉搜索树一致:
向左向右走,遇到空就开始插入,遇到相同值的就返回false(去重效果)

三叉链!!注意链接父亲节点

值得说明的是,这里默认插入节点是红色节点,这是因为

如果插入黑色节点,需要调整每条路径上黑色节点的数量,

上图就有11条路经,此操作不禁令人望而却步

如果插入红色节点,情况就会好很多----

2. 按需变色或旋转+变色

注意四个节点:

cur:当前节点                parent:当前节点的父亲节点

grandfather:当前节点的父亲节点的父亲节点        uncle:当前节点的父亲节点的兄弟节点

后续画图使用 c 、p、g、u 来表示这四个节点

(1)parent不存在

cur 就是根节点,因为默认节点为红色,此时需要修正

if (!_root)
{_root = new Node(kv);//修正_root->_col = BLACK;return true;
}

(2)parent存在且为黑

cur 为红色,红黑相间,不违背红黑树的规则,此时不作其余操作

(3)parent存在且为红

cur 所在路径下存在连续的红色节点,开始修正----

cur为红色,parent为红色,grandfather一定存在且为黑,因为

如果grandfather不存在,则说明在插入cur之前,树结构就出问题了(根节点不为黑色)

如果grandfather存在且为红,则说明在插入cur之前,树结构就出问题了(连续的红色节点)

此时讨论的重点转移到了 uncle 上

(1)uncle 存在且为红

只要cur为红色,parent为红色,grandefather(此时一定)为黑色,uncle为红色

不管cur在parent的左还是右,parent在grandfather的左还是右

统一的处理方法是:将parent、uncle变黑,grandfather变红,再继续向上处理

grandfather 原来是黑色节点,此操作后变红,为了防止路径上出现连续的红节点,还需要继续向上处理 :cur = grandfather;parent = cur->_parent

认准cur为红色,parent为红色,grandefather为黑色,uncle为红色的唯一标识!

(2)uncle 不存在

实现技巧只能是画图分析,不懂旋转的朋友参考 我的博客  AVL树的简介与部分实现

uncle不存在,cur只能是新插入的节点,此时的解决方法是
根据cur在parent的左还是右,parent在grandfather的左还是右,按需旋转变色

cur在parent的,parent在grandfather的:右单旋,parent变黑,grandfather变红

cur在parent的,parent在grandfather的:左单旋,parent变黑,grandfather变红

cur在parent的,parent在grandfather的:左右双旋,cur变黑,grandfather变红

cur在parent的,parent在grandfather的:右左双旋,cur变黑,grandfather变红

旋转完成以后,规则2、3没有被违反,插入结束

(3)uncle存在且为黑

cur一定是变色上来的节点,否则就违反了规则3,

此时的解决方法与uncle不存在的解决方法一致

cur在parent的,parent在grandfather的:右单旋,parent变黑,grandfather变红

cur在parent的,parent在grandfather的:左单旋,parent变黑,grandfather变红

cur在parent的,parent在grandfather的:左右双旋,cur变黑,grandfather变红

cur在parent的,parent在grandfather的:右左双旋,cur变黑,grandfather变红

旋转完成以后,规则2、3没有被违反,插入结束

3.记忆方法

只有当parent存在且为红时,才会按需变色或旋转+变色

细分三种情况讨论,但(2)、(3)两种情况的操作一致,(1)和(2)(3)不一致,所以

区分情况1和情况2、3

情况1和情况2、3是以 讨论 uncle 为中心,而只有通过parent与grandfather的连接情况才能找到uncle,所以

操作的思路是,区分parent的位置并找到uncle,区分情况1和情况2、3

//控制红黑节点
while (parent && parent->_col == RED)
{Node* grandfather = parent->_parent;//区分parent的位置if (parent == grandfather->_left){Node* uncle = grandfather->_right;//区分情况(1)和(2)(3)if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle 不存在 或者 存在且为黑{//		g//	  p//cur if (cur == parent->_left){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//	g//p//	cur else{RotateL(parent);RotateR(grandfather);parent->_col = grandfather->_col = RED;cur->_col = BLACK;}break;}}else//parent == grandfather->right{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle 不存在 或者 存在且为黑{//g//	p//	cur if (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//	g//		p//	cur else{RotateR(parent);RotateL(grandfather);parent->_col = grandfather->_col = RED;cur->_col = BLACK;}break;}}
}
_root->_col = BLACK;

旋转结束即跳出循环没有违反规则,但是在uncle 存在且为红 的向上调整操作中

若parent为空,跳出循环,此时cur即是根节点,但是此时的cur为红色节点,需要进行修正

_root->_col = BLACK;

5.验证红黑树

(1)通过枚举我们已经限制了节点颜色非黑即红

(2)所以我们要验证根节点的颜色为黑色

(3)验证树中没有连续的红色节点

当前节点与其父亲节点的颜色不全为红即可,

(4)验证从任意节点到其所有空节点的路径上,黑色节点数量相同

只要验证根节点到每个空节点的路径上,黑色节点的数目一致即可

沿着最左路径记录黑色节点的数目,然后进行前序遍历,遇到空时就进行比较

bool IsRBTree()
{if (!_root){return true;}//验证根节点的颜色为黑色if (_root->_col != BLACK){cout << "根节点不为黑色" << endl;return false;}//最左路径的黑色节点数目作为基准值int BenchMark = 0;Node* cur = root;while (cur){if (cur->_col == BLACK)++BenchMark;cur = cur->_left;}return CheckColor(_root, 0, BenchMark);
}bool CheckColor(Node* root, int blacknum, int benchmark)
{if (!root){//blacknum是传值传参,//节点为空,blacknum记录的就是根节点到该空节点的路径中黑色节点的数目if (blacknum != benchmark){cout << "不同路径黑色数目不同" << endl;return false;}return true;}if (root->_col == BLACK)++blacknum;//验证树中不存在连续的红色节点if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << " 和它的父亲 " << root->_parent->_kv.first<< " 都是红色节点" << endl;return false;}//前序遍历return CheckColor(root->_left, blacknum, benchmark)&& CheckColor(root->_right, blacknum, benchmark);
}
http://www.dtcms.com/a/353849.html

相关文章:

  • 数值分析离散积分近似求值
  • 【数据分析】微生物群落网络构建与模块划分的比较研究:SparCC、Spearman-RAW与Spearman-CLR方法的性能评估
  • Shell编程-随机密码生成
  • volitale伪共享问题及解决方案
  • SoC如何实现线程安全?
  • 【进阶篇第五弹】《详解存储过程》从0掌握MySQL中的存储过程以及存储函数
  • TypeScript:Interface接口
  • 如何启动一个分支网络改造试点?三步走
  • 【链表 - LeetCode】25. K 个一组翻转链表
  • 干眼症护理学注意事项
  • linux下的网络编程(2)
  • 技术分析 | Parasoft C/C++test如何突破单元测试的隔离难题
  • 亚马逊关键词策略全解析:类型、工具与多账号运营优化指南
  • AT_abc406_f [ABC406F] Compare Tree Weights
  • Windows/Linux 环境下 Jmeter 性能测试的安装与使用
  • 基于SpringBoot的宠物领养服务系统【2026最新】
  • MySQL 面试题系列(五)
  • Unity自定义Inspector面板之使用多选框模拟单选框
  • 前端技术演进录:从 AI 革命到架构新生
  • 【Linux】常用命令 拥有者和权限(四)
  • Python随机选择完全指南:从基础到高级工程实践
  • 安全向量模板类SiVector
  • vue 前端 区域自适应大小
  • AWS申请增加弹性IP配额流程
  • 《Vuejs设计与实现》第 17 章(编译优化)
  • 机器视觉学习-day05-图片颜色识别及颜色替换
  • # 快递单号查询系统:一个现代化的物流跟踪解决方案
  • YOLO12n-Deepsort多目标跟踪之昆虫数据集
  • 【C++标准库】<ios>详解基于流的 I/O
  • 科技赋能生态,智慧守护农林,汇岭生态开启农林产业现代化新篇章