前言
在之前,我们学习了AVL树,知道了AVL树是一个平衡二叉搜索树,如果没学过AVL树,这篇文章看起来会很吃力,不清楚如何旋转的,建议可以先看AVL树的内容。
今天我们要学习的红黑树,他也是一颗平衡二叉搜索树,只不过他不像AVL树那样是绝对平衡的二叉搜索树,而是一种近视平衡,是不是知道这一点后,感觉红黑树也没有那么神秘了。
目录
一、红黑树概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
二、红黑树性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
第1点和第2点都很好理解。不是红就是黑,根为黑。
对于第3点:“如果一个节点是红色,那么它的两个孩子的结点是黑色的”,这句话我们可以翻译一下,他代表着不可能出现两个连续的红色结点。
对于第4点,从某一个结点开始到其所有后代叶节点的路径上,黑色结点的数量相同。这里指的叶子结点是空节点。
如下左图,一共有7条路径(每条路径两个节点)。是红黑树 下右图,乍看一看每条路径都有三个结点,仿佛是一颗红黑树,但是我们忽略了叶子结点是空结点这一性质。
如下,实际情况是最右路径只有两个黑节点,破坏了性质4,每条路径结点个数不一样,因此不算正确的红黑树。
- 那么,有了这几点性质,为何就能保证我们概念里所说到的最长路径不超过最短路径的两倍呢?
因为,最长路径为一黑一红(从第3点可知,不能有连续的红色结点),最短路径为全为黑色(因为有了红色结点就会增加长度)。并且由第4点可知,每一条路径上的黑色结点都相同,如果每条路径上的黑色节点为n个,那么最长路径可以为2n,最短路径可以为n。这样就可以保证最长路径不超过最短路径的两倍。
由此我们可以发现,红黑树的查找效率并不会比AVL树差很多,他们的查找时间复杂度都是log(n) ,只是常数的差距。那么何为Map和Set的底层要用红黑树而不是AVL树呢?因为红黑树查找效率不差,插入效率会高一些,(没有严格的高度差,首先考虑变色,不得已才旋转)。
三、红黑树插入结点
1.红黑树的结点
红黑树的结点也要用到三叉链,就是有左右子树结点,还有父亲节点,还有一个_key值存储数据,和一个_col存放结点是什么颜色(_col用了枚举来方便我们管理)
enum color
{
RED,
BLACK
};
template<class K>
struct RBTreeNode
{
RBTreeNode<K>* _parent;
RBTreeNode<K>* _left;
RBTreeNode<K>* _right;
K _key;
color _col;
RBTreeNode(const K& key)
:_key(key)
,_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_col(RED)
{}
};
对于RBTreeNode的构造函数,_key、_parent、_left、_right的初始化没问题,也一直都能理解,那为什么要将结点的颜色初始化为红色呢? 黑色不行吗?
这也要涉及到红黑树的性质3和4
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
如果新节点为红色,我们可能就会破坏掉第3条性质,如果新节点的父亲为黑,则不需要处理,如果父亲为红,才需要处理。并且我们只会破坏当前路径的规则,对于其他路径并不涉及。
如果新节点为黑色,我们会破坏掉第4条的性质,使得每条路径的黑色结点不同,似乎是对整棵树都会有影响,这样非常不方便我们处理。
因此在选择新结点颜色为红(得罪一个)还是为黑(得罪一群),我们选择为红(得罪一个)
2.找到应该插入的位置
我们将红黑树封装为类,他有私有成员根节点_root。对于应该插入的位置,由于红黑树是一颗二叉搜索树,因此我们可以通过Key值去判断他应该存放在左子树还是右子树,具体情况二叉搜索树中有讲到,可以直接Copy一份过来。
template<class K>
class RBTree
{
typedef RBTreeNode<K> Node;
public:
bool Insert(const K& key)
{
Node* cur = _root;
Node* parent = _root;
if (_root == nullptr)
{
_root = new Node(key);
_root->_col = BLACK;
return true;
}
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
}
private:
Node* _root;
};
3.插入结点
从上面的代码可知,现在已经有了parent的位置了。但不知道应该插在parent的左还是右,这里可以通过parent的_key和我们插入结点所传递的key作比较,父亲的_key比插入的key大就插入在父亲的左边,父亲的_key比插入的key小就插入在父亲的右边。
代码如下
if (parent->_key > key)
{
parent->_left = new Node(key);
cur = parent->_left;
}
else
{
parent->_right = new Node(key);
cur = parent->_right;
}
cur->_parent = parent;
4.判断是否需要处理
我们所插入的新节点是红色的,如果parent为黑色,则不需要处理,parent是红色,才需要处理。(规则3:不能有连续的红节点)那么我们应该如何处理呢??先说答案(看叔叔)
4.1叔叔存在且为红色
如下图,我们插入了cur结点或者cur结点是由下面的节点变色而来,首先应该将父节点p变黑(规则3,不能有连续的红色结点),那么我p变黑了,如果叔叔u的颜色为红色,那么叔叔u也得变成黑色(规则4,每条路径黑色节点相同),那么还得将祖父g变红,目的是让这条路径黑色结点个数没有变(如果不变红,黑色结点个数就从1变成了2),由于g被变红了,可能g的父亲也是红色结点,因此我们要继续往上更新。
最后需要注意一点,如果祖父g是根节点,那么我们将他变成红色就违反了规则2(根节点是黑色),为防止这种情况,可以在while循环结束后,添加上"_root->_col=BLACK;"代码,保证无论如何根节点都给他置为黑色即可。
叔叔存在且为红代码如下
while (parent && parent->_col == RED)
{
Node* grandparent = parent->_parent;
if (grandparent->_left == parent)
{
// g
// p u
// c
Node* uncle = grandparent->_right;
if (uncle && uncle->_col == RED)
{
//变颜色
parent->_col = BLACK;
uncle->_col = BLACK;
grandparent->_col = RED;
//往上更新
cur = grandparent;
parent = grandparent->_parent;
}
}
}
_root->_col=BLACK;
4.2叔叔不存在或者存在是黑色
4.2.1叔叔不存在
如果叔叔不存在,证明cur就为新增结点,并且树的结构一定如下图所示,因为无论在哪个结点左右添加上任意结点,都会破坏红黑树的性质。
这个时候,如果你只通过变色来处理,是不能够保持红黑树的性质的。因为,如果你将父亲p变黑,祖父g变红,如下图,会使得g往右边走的那条路径少一个结点,因此这种处理不可取。
- 那我们应该如何处理呢?
需要用到之前AVL树提到的旋转,如下图,对祖结点g节点进行右旋。最后再将p变黑色,g变红色就完成了,这就保持了红黑树的性质,并且由于p是黑色结点,p的父亲无论是什么颜色,都不影响。可以选择break;防止出错,至于为什么会出错,看下面的。
- 上图中cur为parent的左边,如果cur在parent的右边呢?
先将p结点左旋成为上图的样子,再重复上图的步骤即可。只需要注意这样旋转后cur变成了当前树的根节点,变色应该将cur变黑,g变红,再break退出循环即可(一定要break)。
- 为什么一定要break呢?
一是因为旋转后,已经满足红黑树的性质,不需要再次处理。
二是因为我们外层的循环条件 "parent && parent->_col == red" 已经不适用这种情况了,parent被旋转过,不在是cur的父节点。
4.2.2叔叔存在且为黑
叔叔存在且为黑,他跟上面的叔叔不存在是一样的,区别在于图是抽象图。如下图。
abcde不一定为空,对于祖父结点g来说,如果每条路径有两个黑色结点,那么d和e可能有一个红色结点,或者没有结点,c一定有一个黑色结点为根,这个根的左子树和右子树一样要么不存在要么为红,a和b为空节点。p结点为新增结点(为什么会这样,因为只有这样才不会破坏红黑树的性质)这一段分析应该不难理解(理解不了也没关系)
当然如果p是由下面a,b子树变色而来,情况就很复杂了,因此我们只画了抽象图。
对于上图这种情况依然是跟叔叔不存在一样进行旋转,对祖父g节点进行右旋,这时候p就变成了当前树的根结点,再将p变黑色,g变红色。一样尽量break。
对于cur在parent的右边,跟上面分析的叔叔不存在一样,对父节点p左旋,再对祖结点g右旋,再将g变红色,cur变黑色即可,看下图。
代码如下,旋转代码后续给出。
Node* grandparent = parent->_parent;
if (grandparent->_left == parent)
{
// g
// p u
// c
Node* uncle = grandparent->_right;
if (uncle && uncle->_col == RED)
{
//变颜色
parent->_col = BLACK;
uncle->_col = BLACK;
grandparent->_col = RED;
//往上更新
cur = grandparent;
parent = grandparent->_parent;
}
else
{
// g
// p u
// c
if(cur == parent->_left)
{
RotateR(grandparent);
grandparent->_col = RED;
parent->_col = BLACK;
break;
}
// g
// p u
// c
else
{
RotateL(parent);
RotateR(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
break;
}
}
}
4.2.3总结
刚刚我们讲解了如下grandparent->_left = parent ,父节点在祖父的左,叔叔在右的情况
对于如下grandparent->_right = parent ,父节点在祖父的右,叔叔在左的情况,跟上图刚刚好相反,代码也是类似的,就不再过多赘述了
附上代码
else //grandparent->_right == parent
{
// g
// u p
// c
Node* uncle = grandparent->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
cur = grandparent;
parent = grandparent->_parent;
}
else
{
// g
// u p
// c
if (parent->_right == cur)
{
RotateL(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
break;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
break;
}
}
}
四、判断是否为红黑树
要判断是否为红黑树,我们需要根据它的性质来进行判断。
性质1:非红即黑 ------ 不需要判断,肯定非红即黑
性质2:根为黑 ------- 简单,直接if判断即可
难点在于判断他的性质3和性质4
性质3:不能有连续的红色节点
由于二叉树有左右子树,如果当前结点颜色为红色,我们去判断他的孩子是否为红,因为有两个孩子,并且孩子可能还不存在,这样情况会比较复杂。如果我们选择判断当前结点的父亲是否为红色,这样可以节省很多时间,并且只有根节点没有父亲,其他结点都有父亲,这样遍历较好。
性质4:每条路径黑色结点的个数相同
这个似乎乍眼一看,还挺难判断的,但是也有技巧,我们可以随便找出一条路径的黑色结点个数,记为valRef,再传递给递归函数去判断。同时传递当前黑色结点的个数,记为BlackNum,一开始为0,如果递归函数遇到一个黑结点,就将BlackNum+1,直到走到了空,证明这条路径走完了,可以将valRef和BlackNum进行相等判断,相等就return true,继续遍历,不相等就return false,会递归回来结束遍历。
具体代码如下:
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
//valRef为路径上黑色结点的个数
int valRef = 0;
Node* cur = _root;
while (cur) //一直往某条路径走,找出这条路径黑色结点的个数
{
if (cur->_col == BLACK)
valRef++;
cur = cur->_left;
}
int BlackNum = 0;
return Check(_root,BlackNum,valRef);
}
bool Check(Node* root,int BlackNum,int valRef)
{
if (root == nullptr)
{
if(BlackNum == valRef)
{
return true;
}
else
{
cout << "每条路径黑色结点个数不等" << endl;
return false;
}
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "有连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
BlackNum++;
}
return Check(root->_left,BlackNum,valRef) && Check(root->_right, BlackNum, valRef);
}
五、中序遍历
还有中序递归遍历,这是我们老生常谈的东西,不对赘述了,附上代码
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
六、总代码
附上总代码(左旋右旋也在里面)RBTree.h
#pragma once
enum color
{
RED,
BLACK
};
template<class K>
struct RBTreeNode
{
RBTreeNode<K>* _parent;
RBTreeNode<K>* _left;
RBTreeNode<K>* _right;
K _key;
color _col;
RBTreeNode(const K& key)
:_key(key)
,_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_col(RED)
{}
};
template<class K>
class RBTree
{
typedef RBTreeNode<K> Node;
public:
bool Insert(const K& key)
{
Node* cur = _root;
Node* parent = _root;
if (_root == nullptr)
{
_root = new Node(key);
_root->_col = BLACK;
return true;
}
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parent->_key > key)
{
parent->_left = new Node(key);
cur = parent->_left;
}
else
{
parent->_right = new Node(key);
cur = parent->_right;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandparent = parent->_parent;
if (grandparent->_left == parent)
{
// g
// p u
// c
Node* uncle = grandparent->_right;
if (uncle && uncle->_col == RED)
{
//变颜色
parent->_col = BLACK;
uncle->_col = BLACK;
grandparent->_col = RED;
//往上更新
cur = grandparent;
parent = grandparent->_parent;
}
else
{
// g
// p u
// c
if(cur == parent->_left)
{
RotateR(grandparent);
grandparent->_col = RED;
parent->_col = BLACK;
break;
}
// g
// p u
// c
else
{
RotateL(parent);
RotateR(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
break;
}
}
}
else //grandparent->_right == parent
{
// g
// u p
// c
Node* uncle = grandparent->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
cur = grandparent;
parent = grandparent->_parent;
}
else
{
// g
// u p
// c
if (parent->_right == cur)
{
RotateL(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
break;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
break;
}
}
}
}
_root->_col = BLACK;
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool Check(Node* root,int BlackNum,int valRef)
{
if (root == nullptr)
{
if(BlackNum == valRef)
{
return true;
}
else
{
cout << "每条路径黑色结点个数不等" << endl;
return false;
}
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "有连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
BlackNum++;
}
return Check(root->_left,BlackNum,valRef) && Check(root->_right, BlackNum, valRef);
}
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
int valRef = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
valRef++;
cur = cur->_left;
}
int BlackNum = 0;
return Check(_root,BlackNum,valRef);
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* grandparent = parent->_parent;
parent->_parent = subL;
if (grandparent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
return;
}
if (grandparent->_left == parent)
{
grandparent->_left = subL;
}
else
{
grandparent->_right = subL;
}
subL->_parent = grandparent;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* grandparent = parent->_parent;
parent->_parent = subR;
if (grandparent == nullptr)
{
_root = subR;
_root->_parent = nullptr;
return;
}
if (grandparent->_left == parent)
{
grandparent->_left = subR;
}
else
{
grandparent->_right = subR;
}
subR->_parent = grandparent;
}
Node* _root = nullptr;
};
test.cpp文件
#include<iostream>
#include<vector>
#include<ctime>
using namespace std;
#include"RBTree.h"
//int main()
//{
// RBTree<int> rbt;
// int arr[] = { 7, 6, 2, 1, 3, 6, 8, 9, 7, 10 };
// for(auto e : arr)
// {
// rbt.Insert(e);
// }
// rbt.InOrder();
// cout << rbt.IsBalance() << endl;
// return 0;
//}
int main()
{
const int N = 1000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand() + i);
}
RBTree<int> rbt;
for (auto e : v)
{
if (e == 24473)
{
int i = 0;
}
rbt.Insert(e);
rbt.IsBalance();
}
rbt.InOrder();
cout << rbt.IsBalance() << endl;
return 0;
}
测试
感谢大家观看!!!