红黑树的概念
红黑树,是一棵二叉搜索树,但在每一个结点上增加一个存储位表示结点的颜色,可以是Red或Black.通过任何一条从根到叶子上的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的.
红黑树的性质
1: 每个结点不是红色就是黑色.
2: 根节点是黑色的
3: 如果一个结点是红色的,则它的两个孩子结点是黑色的.
4: 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点.
5: 每个叶子结点都是黑色的(此处的叶子结点指的是NIL结点,也就是空结点).
依据以上5个性质,怎么保证红黑树的最长路径不会超过最短路径的两倍呢?
我们可以假设,在红黑树中,如果每个路径的黑色结点都是N个,那么在棵红黑树中最短路径长度就为N.(假设N为2)
根据性质3,性质4,那么最长路径也是N个黑色结点,并且由多对一黑一红结点组成,此时,最长路径的长度即为2N.(假设N为2)
所以,在红黑树中,最长路径长度等于最短路径长度的两倍,不可能超过最短路径的两倍.
红黑树结点的定义
我们实现的是KV模型结构的红黑树,由于红黑树需要旋转,我们将红黑树的结点定义为三叉链结构,并且添加一个新成员_col,代表结点的颜色,方便后续的变色平衡.
enum Colour
{
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; //存储的键值对
Colour _col; //标志着红黑树的颜色.
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_col(RED) //将结点默认定义为红色.
{
}
};
思考: 在结点的定义中,为什么要将结点的默认颜色给成红色的?
1:如果我们在红黑树的一条路径中插入一个黑色结点,由于性质4,那么便会影响红黑树的其它路径,为了保持平衡,其他路径都可能需要变色+旋转.
2: 如果我们在红黑树的一条路径中插入一个红色结点,由于性质三,最多只会影响插入的那一条路径,我们只需要对该条路径变色+旋转来保持平衡就行.
所以,为了将红黑树的插入影响降到最低,我们选择将红黑树的结点默认定义为红色.
红黑树的插入
红黑树的插入主要分为三个步骤:
1: 找到红黑树的插入位置.
2: 将插入结点插入到寻找的插入位置.
3: 检测新结点插入后,红黑树的性质是否发生改变,并对其进行相应调整.
其中步骤1和步骤2和AVL树的插入相同,但是步骤三相较AVL树的插入有了较大改变.
那么查看红黑树性质的依据是什么?
在红黑树的插入中:
1: 如果插入结点的父亲结点是黑色的,那么该次插入并没有破坏红黑树的性质,则该次插入并不需要调整平衡.
2: 如果插入结点的父亲结点是红色的,那么根据红黑树的性质2,父亲结点一定有grandfater结点且为黑色结点.
3: 如果插入结点为父亲结点是红色的,那么根据插入结点的叔叔结点(即parent的兄弟结点)的情况,进行调整平衡.
此时有三种情况:
情况一: 插入结点的叔叔存在,并且叔叔结点的颜色为红色.
为了防止出现连续的红结点,我们需要将父亲结点变黑,又为了保持每条路径的黑色结点的个数相同,我们将祖父结点变红,将叔叔结点变黑.因此,我们既可以解决连续出现红色结点的问题,又可以保持每条路径的黑色结点相同.
变色调整抽象图如下:
注意:
1:如果此时的祖父结点即为整棵树的根结点,:那么为了保持根节点为黑结点的性质,我们需要将祖父变为黑色结点后,退出循环.
2: 如果此时的祖父结点为整棵树的子树,那么我们需要将cur指向祖父节点,parent指向cur的父亲结点后,根据叔叔的情况继续平衡.
3: 当叔叔存在且为红色结点时,cur是parent的左孩子还是右孩不影响变色平衡.
情况二: 插入结点的叔叔存在,并且叔叔结点的颜色为黑色.
插入前:
1: d,e 可以是空树或者是一个红色结点.
2: c可以是根是黑节点的子树,为例图右侧四种子树的任意一种.
插入后
由插入前后例图可见,情况二的出现主要由情况一插入时变色调整导致的.
当叔叔存在且为黑色结点时,此时需要旋转+变色调整平衡,但是旋转时打又有两种情况:
(1):如果祖先,父亲,儿子三个结点的路径为一条直线,首先,于祖父所在的红黑树来说,左子树的路径的长度已经大于最短路径的两倍了,此时需要以祖先为旋转点右单旋(降左子树的高度),为了保持红黑树的每条路径的黑色结点个数相等,我们需要将父亲结点调整为黑色结点,再将祖父结点调整为红色结点.
旋转+变色抽象图如下:
(2)如果祖先,父亲,儿子三个结点的路径为一条折线,我们需要先以父亲为旋转点左单旋,然后再以祖父为旋转点右单旋,将祖父调整为红色结点,将孩子结点调整为黑色结点,进而保持了每条路径的的的黑色结点个数相同.
注意:
颜色调整后,无论这棵树是整棵树还是子树,根节点是黑色的,且已经保持平衡,所以无需继续往上处理.
情况三: 插入结点的叔叔不存在,且a/b/c/d/e为空树
情况三又分为两种情况:
1:如果祖父,父亲,孩子结点组成的路径为一条直线:
首先祖父结点左子树的路径长度大于最短路径的两倍,此时需要以祖父右单旋降左子树高度,然后将父亲结点调整为黑色结点,将祖父结点调整为红色结点.
2:如果祖父,父亲,孩子结点组成的路径为一条折线.
同理,首先,我们先以父亲结点为旋转点左单旋,然后再以祖父为旋转点右单旋,最后,再将孩子结点调整为黑色结点,祖父结点调整为红色结点.
红黑树的插入函数代码如下:
bool Insert(const pair<K, V>& kv) //插入函数
{
if (_root == nullptr) //如果为空树,直接插入结点就可以了.
{
_root = new Node(kv);
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 < kv.first) //与父亲结点建立联系
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while ( parent && parent->_col == RED ) //达到平衡条件,需要调整平衡.
{
Node* grandfater = parent->_parent;
assert(grandfater);
assert(grandfater->_col == BLACK);
if ( parent == grandfater->_left ) //判断父亲在祖父的位置,如果父亲在祖父的左边
{
Node* uncle = grandfater->_right;
if ( uncle && uncle->_col == RED ) //将父亲和叔叔的颜色变黑,祖父的颜色变红.
{
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater; //继续向上寻找
parent = grandfater->_parent;
}
else //uncle不存在或者存在且为黑
{
// g
// p
//cur
if (cur == parent->_left) //如果祖父,父亲,孩子路径为一条直线
{
RotateR(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
// g
// p
// cur
else //如果祖父,父亲,孩子路径为一条折线.
{
RotateL(parent);
RotateR(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
}
//走到这里大该子树的根已经变成黑色结点了,不用再往上循环了.
break;
}
}
else //如果父亲在祖父的右边.
{
// g
// u p
// c
//
Node* uncle = grandfater->_left;
if (uncle && uncle->_col == RED) //如果叔叔存在且为红色结点.
{
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater; //继续向上变色循环.
parent = grandfater->_parent;
}
else //如果父亲不存在,或者存在且为黑色结点.
{
// g
// p
// c
if (parent->_right == cur) //如果父亲,祖父,孩子路径为一条直线
{
RotateL(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
// g
// p
// c
else //如果父亲,祖父,孩子路径为一条折线
{
RotateR(parent);
RotateL(grandfater);
grandfater->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
_root->_col = BLACK; //走到这说明这棵树没有的祖先没有parent,或者有parent但是父亲是黑色的,不需要处理.
return true;
}
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 (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
}
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 (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
红黑树的查找
查找步骤:
1: 如果所给值大于cur所指结点first,那么从左边遍历查找.
2: 如果所给值大于cur所指结点first,那么从右边遍历查找.
3: 找到了就返回.
4: 但退出循环还没找到,就说明找不到了,返回nullptr.
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key ) //如果key大于当前结点的first
{
cur = cur->_right;
}
else if (cur->_kv.first > key) //如果key小于当前结点的first
{
cur = cur->_left;
}
else //寻找到了
{
return cur;
}
}
return nullptr;
}
红黑树的验证
检测是否满足二叉搜索树
红黑树也是一棵接近平衡的二叉搜索树,因此,我们可以通过中序遍历,进而查看是否满足二叉搜索树的性质.
void InOrder() //中序遍历
{
_InOrder(_root);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":"<< root->_kv.second << endl;
_InOrder(root->_right);
}
检测是否满足红黑树的性质
我们可以检测红黑树的四个性质进而推出红黑树的最长路径长度不超过最短路径的两倍.最后可以判断是否平衡.
1: 性质一可以通过代码的颜色枚举中证明.
2: 性质二和性质三可以在递归中进行判断.
3: 性质四,我们可以设计一个基准值,先求出第一条路径的黑色结点个数为基准值,然后当其他的路径走完时,将其他路径黑色结点个数与该基准值分别比较.
bool PreCheck(Node* root,int& benchMark, int blackNum) //判断性质四
{
if (root == nullptr)
{
if (benchMark == 0)
{
benchMark = blackNum;;
return true;
}
if (blackNum != benchMark)
{
cout << "某条黑色结点数量不相等." << endl;
return false;
}
else
{
return true;
}
}
if ( root->_col == RED && root->_parent->_col == RED )//判断性质三
{
return false;
}
if (root->_col == BLACK)
{
++blackNum;
}
return PreCheck(root->_left,benchMark ,blackNum) &&
PreCheck(root->_right,benchMark, blackNum);
}
bool _IsBalance( Node* root ) //要从红黑树的四个性质分别判断
{
if (root == nullptr )
{
return true;
}
if (root->_col == RED) //判断性质一
{
cout << "根节点不是黑色结点" << endl;
return false;
}
int benchMark = 0; //黑色结点数量基准值.
return PreCheck(root,benchMark,0);
}
红黑树与AVL树的比较
1:红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都为O(log2N),红黑树不追求绝对平衡,只需要保证最长路径的长度不超过最短路径的两倍.
2:在插入或者删除中,红黑树降低了旋转的次数,所以需要多次增删操作中,红黑树的性能比AVL树的性能更加高效,而且红黑树的实现相对AVL树来说比较简单,在实践中使用红黑树更多.
包含上述功能的红黑树代码
#include <iostream>
#include <assert.h>
#include <map>
using namespace std;
enum Colour
{
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; //存储的键值对
Colour _col; //标志着红黑树的颜色.
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
{
}
};
template <class K, class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
public:
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key ) //如果key大于当前结点的first
{
cur = cur->_right;
}
else if (cur->_kv.first > key) //如果key小于当前结点的first
{
cur = cur->_left;
}
else //寻找到了
{
return cur;
}
}
return nullptr;
}
void InOrder()
{
_InOrder(_root);
}
bool Insert(const pair<K, V>& kv) //插入函数
{
if (_root == nullptr) //如果为空树,直接插入结点就可以了.
{
_root = new Node(kv);
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 < kv.first) //与父亲结点建立联系
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while ( parent && parent->_col == RED ) //达到平衡条件,需要调整平衡.
{
Node* grandfater = parent->_parent;
assert(grandfater);
assert(grandfater->_col == BLACK);
if ( parent == grandfater->_left ) //判断父亲在祖父的位置,如果父亲在祖父的左边
{
Node* uncle = grandfater->_right;
if ( uncle && uncle->_col == RED ) //将父亲和叔叔的颜色变黑,祖父的颜色变红.
{
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater; //继续向上寻找
parent = grandfater->_parent;
}
else //uncle不存在或者存在且为黑
{
// g
// p
//cur
if (cur == parent->_left) //如果祖父,父亲,孩子路径为一条直线
{
RotateR(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
// g
// p
// cur
else //如果祖父,父亲,孩子路径为一条折线.
{
RotateL(parent);
RotateR(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
}
//走到这里大该子树的根已经变成黑色结点了,不用再往上循环了.
break;
}
}
else //如果父亲在祖父的右边.
{
// g
// u p
// c
//
Node* uncle = grandfater->_left;
if (uncle && uncle->_col == RED) //如果叔叔存在且为红色结点.
{
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater; //继续向上变色循环.
parent = grandfater->_parent;
}
else //如果父亲不存在,或者存在且为黑色结点.
{
// g
// p
// c
if (parent->_right == cur) //如果父亲,祖父,孩子路径为一条直线
{
RotateL(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
// g
// p
// c
else //如果父亲,祖父,孩子路径为一条折线
{
RotateR(parent);
RotateL(grandfater);
grandfater->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
_root->_col = BLACK; //走到这说明这棵树没有的祖先没有parent,或者有parent但是父亲是黑色的,不需要处理.
return true;
}
bool IsBalance()
{
return _IsBalance(_root);
}
private:
bool PreCheck(Node* root,int& benchMark, int blackNum) //判断性质四
{
if (root == nullptr)
{
if (benchMark == 0)
{
benchMark = blackNum;;
return true;
}
if (blackNum != benchMark)
{
cout << "某条黑色结点数量不相等." << endl;
return false;
}
else
{
return true;
}
}
if ( root->_col == RED && root->_parent->_col == RED )//判断性质三
{
return false;
}
if (root->_col == BLACK)
{
++blackNum;
}
return PreCheck(root->_left,benchMark ,blackNum) &&
PreCheck(root->_right,benchMark, blackNum);
}
bool _IsBalance( Node* root ) //要从红黑树的四个性质分别判断
{
if (root == nullptr )
{
return true;
}
if (root->_col == RED) //判断性质一
{
cout << "根节点不是黑色结点" << endl;
return false;
}
int benchMark = 0; //黑色结点数量基准值.
return PreCheck(root,benchMark,0);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":"<< root->_kv.second << endl;
_InOrder(root->_right);
}
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 (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
}
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 (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
Node* _root = nullptr;
};
//测试1
void TestRBTree1()
{
int a[] = { 16,3,7,11,9,26,18,14,15,0,1 };
RBTree<int, int> t1;
for (auto e : a)
{
t1.Insert(make_pair(e, e));
}
t1.InOrder();
cout << "IsBalance:" << t1.IsBalance() << endl;
}
//测试2
//随机数测试,使用10000个随机数测试
void TestRBTree2()
{
size_t N = 10000;
srand(time(0));
RBTree<int, int> t1;
for (size_t i = 0; i < N; ++i)
{
int x = rand(); //x接收产生的随机数.
t1.Insert(make_pair(x, i)); //在树中插入随机数.
}
t1.InOrder();
cout << t1.Find(1)->_kv.first << endl;
cout << "IsBanlance:" << t1.IsBalance() << endl;
}