数据结构之红黑树
Posted 小赵小赵福星高照~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之红黑树相关的知识,希望对你有一定的参考价值。
红黑树
文章目录
本篇博文只讲解了红黑树的插入操作,下面我们来了解一下什么是红黑树。
红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
NIL是空结点,这里显示出来是为了表示路径
红黑树和AVLTree不同点:
AVLTree:左右高度差不超过1,严格平衡
红黑树:最长路径不超过最短路径的2倍,近似平衡
严格来说红黑树的效率没有AVLTree高,当AVLTree的旋转多时,此时红黑树的效率就高于AVLTree
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点必须是黑色的
树里面没有连续的红色节点
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
每条路径都有相同数量的黑色节点(路径不是走到叶子节点,而是走到空节点)
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点),就像图中的NIL节点
那么是红黑树是如何做到平衡的呢?
通过性质3和4:
由性质四可知,每条路径都有相同的黑色节点,假设有一条路径上都是黑色节点,那么一棵红黑树的最短路径就是这条路径
由性质三可知,红黑树中没有连续的红色节点,那么一棵红黑树的最长路径就是一黑一红,交替出现,因为每条路径的黑色节点是相同的,最长路径不会超过最短路径的两倍
红黑树节点的定义
红黑树节点的颜色我们可以用枚举来定义,我们将红黑树实现成三叉链的结构:
#pragma once
enum Colour
BLACK,
RED
;
template<class K,class V>
struct RBTreeNode
RBTreeNode<K,V>* _left;
RBTreeNode<K,V>* _right;
RBTreeNode<K,V>* _parent;
Colour _col;//颜色
pair<K,V> _kv;//键值对
//构造函数
RBTreeNode(const pair<K,V>& kv)
:_left(nullptr),
:_right(nullptr),
:_parent(nullptr),
:col(RED),
:_kv(kv)
;
有一个问题:为什么颜色初始化,初始化成红色呢?
插入红色可能破坏规则3,但是插入黑色的话一定破坏规则4,因为如果插入黑色节点,该路径黑色节点的数目肯定发生了变化,为了保证所有路径黑色节点数目相同,所有路径都需要做调整,而插入红色,如果父亲节点是黑色节点,那么就不需要做调整,即使出现了连续的红色节点,破坏了规则3,只是破坏了这一条路径,只需要调整该路径,所以选择红色节点
红黑树的插入情况操作
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
- 插入情况一: cur为红,p为红,g为黑,u存在且为红
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
插入cur时,已经出现了连续的红节点,所以需要将p变黑,为了保证不同路径的黑色节点数目不变,u也得变黑,那么为什么最后将g改为红呢?因为这棵树可能只是局部的子树,那么要保持他的每条路径黑色节点的数量不变,p和u变黑以后,路径上的黑色节点多了,所以g得变红,g变红一会就得继续往上处理
具象图1:
处理结果:
具象图二(需要继续往上处理的场景):
如果g是根节点,调整完成后,需要将g改为黑色
如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整
- 插入情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑
关键看叔叔,情况二相比情况一,只变了叔叔
u存在且为黑:
u存在且为黑的情况时,cur一定不是新增,cur原来是黑的,保证了规则4,cur之所以是红色,是因为cur是作为子树的祖父,是由第一种情况变化过来的
该情况是由情况一转变过来的:
u不存在:
u不存在,那么cur就是新增,因为cur不是新增的话,那么p和cur当中一定有一个黑色节点,此时黑色节点就多了,不满足规则4:每条路径黑色节点数目相同
处理方法:
叔叔不存在时:
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转,p、g变色–p变黑,g变红
叔叔存在且为黑:
- 插入情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑
插入情况三相比于插入情况二不一样的是,p如果是p的左边,那么cur就是p的右边,p如果是p的右边,那么cur就是p的左边
这种情况做一次旋转是解决不了问题的,需要做两次旋转:
p为g的左孩子,cur为p的右孩子时,则针对p做左单旋转,然后再针对g做右单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,再针对g做左单旋转
u不存在:
u存在且为黑:
红黑树的插入代码
bool Insert(const pair<K, V>& kv)
if (_root == nullptr)
_root = new Node(kv);
_root->_col = BLACK;//根节点必须是黑色
return true;
Node* parent = nullptr;
Node* cur = _root;
while (cur)
if (cur->_kv.first < kv.first)
parent = cur;
cur = cur->_right;
else if (cur->_kv.first > kv.first)
parent = cur;
cur = cur->_left;
else
//相等
return false;
cur = new Node(kv);
cur->_col = RED;//默认给红色
//链接
if (parent->_kv.first < cur->_kv.first)
parent->_right = cur;
cur->_parent = parent;
else
parent->_left = cur;
cur->_parent = parent;
//控制平衡,控制颜色
while (parent && parent->_col == RED)
Node* grandfather = parent->_parent;
if (parent == grandfather->_right)
//找到叔叔
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,进行变色处理即可,并且继续往上更新处理
if (uncle && uncle->_col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
//情况二+三:uncle不存在,uncle存在且为黑,进行旋转+变色处理
else
//情况二:单旋+变色
if (cur == parent->_left)
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
else
//情况三:双旋+变色
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
break;
else//parent == greandfather->_left
Node* uncle = grandfather->_right;
if (uncle && uncle->col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
else
if (parent->_right == cur)
RotateL(grandfather);
parent->_col = BLACK;
grandfather->COL = RED;
else//parent->_left == cur
//双旋+变色
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
//不用再继续了,不会有连续红色和黑色个数不一样
break;
_root->_col = BLACK;
void RotateR(Node* parent)
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
_root = subL;
subL->_parent = nullptr;
else
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
void RotateL(Node* parent)
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parent == _root)
_root = subR;
subR->_parent = nullptr;
else
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
红黑树测试代码
void TestRBTree1()
//int a[] = 16, 3, 7, 11, 9, 26, 18, 14, 15 ;
//int a[] = 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 ;
const int n = 1000000;
vector<int> a;
a.reserve(n);
srand(time(0));
for (size_t i = 0; i < n; ++i)
a.push_back(rand());
RBTree<int, int> t1;
for (auto e : a)
t1.Insert(make_pair(e, e));
cout << t1.IsBalance() << endl;
//t1.InOrder();
void _InOrder(Node* root)
if(root == nullptr)
return;
_InOrder(root->_left);
cout<<root->_kv.first<<endl;
_InOrder(root->_right);
void InOrder(Node* root)
_InOrder(_root);
bool CheckRR(Node* cur)
if(cur == nullptr)
return true;
if(cur->_col == RED && cur->_parent == RED)
cout<<"违反规则三,存在连续的红色节点"<<endl;
return false;
return CheckRR(cur->_left)
&& CheckRR(cur->_right);
bool CheckBlackNum(Node* cur,int blackNum,int benchmark)
if(cur == nullptr)
if(blackNum != benchmark)
cout<<"违反规则四:黑色节点的数量不相等"<<endl;
return false;
return true;
if(cur->_col == BLACK)
++blackNum;
return CheckBlackNum(cur->_left,balckNum,benchmark);
&& CheckBlackNum(cur->_right,balckNum,benchmark);
bool IsBalance()
if(_root == nullptr)
return true;
if(_root->_col == RED)
cout<<"根节点是红色,违反规则二"<<endl;
return false;
//算出最左路径的黑色节点的个数作为基准值
int benchmark = 0;
Node* cur = _root;
while(cur)
if(cur->_col == BLACK)
++benchmark;
cur = cur->_left;
int blackNum = 0;
//检查规则三
return CheckRR(_root) && CheckBlackNum(root,blackNum,benchmark);
//每条路径的黑色节点都求出来,提前算出一个基准值进行比较
红黑树整体代码
template<class K,class V>
class RBTree
typedef RBTreeNode<K,V> Node;
public:
RBTree()
:_root(nullptr)
bool Insert(const pair<K,V>& kv)
if(_root == nullptr)
_root = new Node(kv);
_root->_col = BLACK;
return true;
Node* parent = nullptr;
Node* cur = _root;
while(cur)
if(cur->_kv.first < kv.first)
parent = cur;
cur = cur->_right;
else if(cur->_kv.first > kv.first)
parent = cur;
cur = cur->_left;
else
//相等了,已经有了
return false;
//插入红色可能破坏规则3,插入黑色一定破坏规则4,破坏规则4比较难处理
//新增节点就新增一个红色节点
//1、如果新增节点的父亲是黑色,没有影响
//2、如果新增节点的父亲是红色,则产生了连续的红色节点,破坏了规则3,需要分情况处理:
cur = new Node(kv);
cur->_col = RED;
//插入链接
if(parent->_kv.first < kv.first)
parent->_right = cur;
cur->_parent = parent;
else
parent->_left = cur;
cur->_parent = parent;
//控制平衡,控制颜色
while(parent && parent->_col == RED)
Node* grandfather = parent->_parent;
if(parent == grandfather->_right)
//找到叔叔
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,进行变色处理即可,并且继续往上更新处理
if(uncle && uncle->_col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
//情况二+三:uncle不存在,uncle存在且为黑,进行旋转+变色处理
else
//情况二:单旋+变色
if(cur == parent->_left)
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
else
//情况三:双旋+变色
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
break;
else//parent == greandfather->_left
Node* uncle = grandfather->_right;
if(uncle && uncle ->col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
else
if(parent->_right == cur)
RotateL(grandfather);
parent->_col = BLACK;
grandfather->COL = RED;
else//parent->_left == cur
//以上是关于数据结构之红黑树的主要内容,如果未能解决你的问题,请参考以下文章