目录
1. 红黑树
1.1 红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。
1.2 红黑树的性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(即没有连续红节点)
4. 每条路径的黑色节点数目相等。
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,即上图中的 NIL 节点)。
只要满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点
个数的两倍。其中最短路径一定是连续黑色节点,最长路径一定是红黑节点交替出现。
1.3 红黑树节点的定义
因为节点颜色只有红黑两种,所以我们把表示节点颜色的变量用枚举类型来表示,另外节点用三叉链表示:即含有三个指针分别指向左右孩子即父节点。还需一个键值对表示节点的key,value值。
enum Color
{
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode* _left;//指向左子树
RBTreeNode* _right;//指向右子树
RBTreeNode* _parent;指向父节点
pair<K, V> _kv;//节点值
Color _col;//颜色
RBTreeNode(const pair<K, V>& kv)//拷贝构造
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
{}
};
1.4 红黑树的插入操作
思路如下:
首先:插入节点应该是什么颜色:黑色 or 红色?
这里我们选择插入红色节点。理由:若插入黑色节点,因为要满足所有路径黑色节点都相同,整棵树直接就不满足了,需要改动很大,不可取;而若是插入红色节点,首先黑色节点数目不被影响,那么接下来只需要保证没有连续红色节点出现就行,我们可以通过从该节点往根节点方向遍历实现。
接下来,我们在默认插入节点为红色的情况下分类讨论插入情况 :
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
情况一: cur为红,p为红,g为黑,u存在且为红
这里 a, b, c, d, e 可以为空也可以为子树,若为空,则cur就是要插入节点,若为子树,则子树要使得整棵树满足红黑树规则:每条节点黑色数目相同。
要满足不能出现连续红节点,我们需要把 p 节点改为黑,又因为因为要满足每条路径黑色节点数目相等,则需要在把 p 改为黑的同时,把u节点改为黑把 g 节点改为红。
接下来,只需要看 g 节点的父节点情况:
如果g节点为根,只需要把g节点改为黑即可;
如果g是子树,则一定有父节点,其父节点若为黑就不管,若为红继续向上调整。
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
说明:此处若p为g左孩子,则cur为p左孩子;若p为g右孩子,则cur为p右孩子。
若 u 不存在,则需要右旋转+变色:
若 u 存在,情况一样:
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
说明:此处若p为g左孩子,则cur为p右孩子;若p为g右孩子,则cur为p左孩子。
若u不存在:
若u存在且为黑:
代码如下:
template<class K, class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
public:
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走到要插入的位置
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* grandfather = parent->_parent;
assert(grandfather);
assert(grandfather->_col == BLACK);
//关键看叔叔
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//情况一:uncle存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else//情况二+三:uncle不存在+存在且为黑
{
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
//变色
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);//以p为轴点左旋
RotateR(grandfather);//以g为轴点右旋
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else//parent == grandfather->_right
{
Node* uncle = grandfather->_left;
//情况一
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else//情况二+三:uncle不存在+存在且为黑
{
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
//变色
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
//根节点一定为黑
_root->_col = BLACK;
return true;
}
private:
void RotateL(Node* parent)//左旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)//subRL可以为空,所以加条件
{
subRL->_parent = parent;
}
//parnet 可能为根也可以不是,记录一下
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可以为空,所以加条件
{
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;
}
}
private:
Node* _root = nullptr;
};
1.5 红黑树的验证
红黑树验证思路有两种:
1. 看最长路径节点数是否超过最短路径节点数2倍。
2. 看每条路径黑色节点数目是否相等。
那么我们应该选择那种方式呢?
这里我们选择第二种,因为如果最长路径节点数不超过最短路径节点数2倍,并不一定满足颜色规则;相反,如果满足颜色规则,则一定满足最长路径节点数不超过最短路径节点数2倍
验证代码如下:
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;
}
return PrevCheck(_root, 0,benchmark);
}
private:
//blackNum记录根节点到当前节点的黑色节点数量
bool PrevCheck(Node* root, int blackNum,int benchmark)
{
if (root == nullptr)
{
if (blackNum != benchmark)
{
cout << "某条黑色节点数量不相等" << endl;
return false;
}
else
{
return true;
}
}
if (root->_col == BLACK)
{
++blackNum;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "存在连续红色节点" << endl;
return false;
}
return PrevCheck(root->_left, blackNum,benchmark)
&& PrevCheck(root->_right, blackNum,benchmark);
}