本章我们将来介绍一下传说中的 “红黑树”
同样红黑树也是一棵 “二叉搜索树”
前情回顾:AVL树复习 传送门
概念:
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black,所以叫红黑树。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树一共有五大特性:
思考问题:
为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
红黑树是怎样实现 最长路径 <= 最短路径的两倍 呢?
最短路径就是全是黑结点的情况,最长路径是一黑一红结点间隔排列的情况。
红黑树:
AVL树:
单方面从查找来说:
AVL树查找的效率会更高,但是代价是AVL树插入时旋转了很多很多次才达到的平衡但是CPU足够的快,20次和40次差别不大。
综合来看:
红黑树更胜一筹,在保证查找效率底线的同时,插入时旋转次数更少。
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
KV模型:每一个关键码key,都有与之对应的值Value,即
比如:英汉词典就是英文与中文的对应关系:
我们还是实现的三叉链,颜色我们通过枚举类型来实现:
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)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
{}
};
这里结点一开始给红色的原因我们下文讲解。
为什么新增的结点要是红色的呢?
这里就要来解释一下为什么构造新的结点的时候为啥默认颜色要给红色的。
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点。
这里用到的思路和AVL树用到的思路大致相同,都是不断地向上更新。
插入几大流程:
其中需不需要调整主要是看叔叔颜色的情况。
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
首先我们知道,红黑树调整主要看叔叔,第一步我们将父亲变成黑色,为了维护性质4,我们也要将叔叔的颜色变成黑色,同时也要将祖父结点的颜色变成红色,因为我们此时处理的不一定是整棵树,有可能是某个子树,如果此时不是子树,就需要将根节点变成黑色。
情况一:不关心左右关系,只变色、不旋转,p、u是g的左和右是无所谓的,cur是p的左或者右也是一样的。
情况一的调整变化是为了变到其他的情况。
情况二可能就是从情况一调整变化得到的情况。
当叔叔不存在时:
如图所示:如果单纯的按照情况一方式的调整的话,还是 违背了性质4,此时我们就想到了旋转的操作方式。
当叔叔存在且为黑时:
这种情况一定是由情况一变来的
解释:
我们用反证法,假设是在插入结点之前就有了情况二的样子
同样是 叔叔不存在 or 叔叔存在且为黑,为啥分为情况二和情况三呢?
上述旋转的情况都是单边的情况,也就是说,cur、p、g在一条线上的情况,若是出现了这三者不在一条直线的时候,单旋就解决不了问题了。
区别:
此时有个疑问:情况二和情况三看样子都违背了红黑树的性质4,难道没错吗?
再此之前我们推测过当叔叔存在且为黑时,一定是由情况一变来的,所以cur一开始是黑的,这个树并不违反性质,子树由情况一变化之后的子树结点的颜色也相应变化了,只是没有显示出来而已。
bool Insert(const pair<K, V>& kv)
{
//1、搜索树的规则插入
//2、看是否违反平衡规则,如果违反就需要处理:旋转
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 < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
//三叉链的链接 -- 链上父节点
cur->_parent = parent;
//...
//存在连续红色结点
while (parent && parent->_col == RED)
{
//理论而言,祖父是一定存在的,父亲存在且是红不可能是根(根一定是黑的)
Node* grandfather = parent->_parent;
assert(grandfather);
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//情况一:(叔叔存在且为红)
if (uncle && uncle->_col == RED)
{
//父亲和叔叔变成黑色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
//情况二:(叔叔不存在 or 叔叔存在且为黑)
else if (uncle == nullptr || uncle->_col == BLACK)
{
//单旋
// g
// p
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//双旋
// g
// p
// c
else if (cur == parent->_right)
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
//无论父亲和叔叔是左是右都是一样的
//grandfather->_right == parent;
else if(grandfather->_right == parent)
{
Node* uncle = grandfather->_left;
//情况一:
if (uncle && uncle->_col == RED)
{
//祖父和叔叔变成黑色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
//情况二:(叔叔不存在 or 叔叔存在且为黑)
else if (uncle == nullptr || uncle->_col == BLACK)
{
//单旋
// g
// p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//双旋
// g
// p
// c
else if (cur == parent->_left)
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
//父亲为空就出循环,将根节点设置成黑色
_root->_col = BLACK;
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 (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (parent == ppNode->_left)
{
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 (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
我们通过一段代码来验证一下红黑树:
首先我们先将整个树遍历一遍来观察一下是否符合搜索二叉树:
中序遍历红黑树:
//中序遍历
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
//求红黑树的最长路径
int _maxHeight(Node* root)
{
if (root == nullptr)
return 0;
int lh = _maxHeight(root->_left);
int rh = _maxHeight(root->_right);
return lh > rh ? lh + 1 : rh + 1;
}
//求红黑树的最短路径
int _minHeight(Node* root)
{
if (root == nullptr)
return 0;
int lh = _minHeight(root->_left);
int rh = _minHeight(root->_right);
return lh < rh ? lh + 1 : rh + 1;
}
void Height()
{
cout << "最长路径:" << _maxHeight(_root) << endl;
cout << "最短路径:" << _minHeight(_root) << endl;
}
//层序遍历
vector<vector<int>> levelOrder()
{
vector<vector<int>> vv;
if (_root == nullptr)
return vv;
queue<Node*> q;
int levelSize = 1;
q.push(_root);
while (!q.empty())
{
// levelSize控制一层一层出
vector<int> levelV;
while (levelSize--)
{
Node* front = q.front();
q.pop();
levelV.push_back(front->_kv.first);
if (front->_left)
q.push(front->_left);
if (front->_right)
q.push(front->_right);
}
vv.push_back(levelV);
for (auto e : levelV)
{
cout << e << " ";
}
cout << endl;
// 上一层出完,下一层就都进队列
levelSize = q.size();
}
return vv;
}
测试1:
void TestRBTree1()
{
//int a[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
int a[] = { 30, 29, 28, 27, 26, 25, 24, 11, 8, 7, 6, 5, 4, 3, 2, 1 };
RBTree<int, int> t;
for (auto e : a)
{
t.Insert(make_pair(e, e));
}
t.levelOrder();
t.InOrder();
t.Height();
}
//判断是否符合红黑树
bool IsBalanceTree()
{
//检查红黑树几条规则
Node* pRoot = _root;
//空树也是红黑树
if (nullptr == pRoot)
return true;
//检测根结点是否满足情况
if (BLACK != pRoot->_col)
{
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
//获取任意一条路径中黑色节点的个数 -- 作为比较基准值
size_t blackCount = 0;
Node* pCur = pRoot;
while (pCur)
{
if (BLACK == pCur->_col)
blackCount++;
pCur = pCur->_left;
}
//检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
}
//上一个函数的返回值是调用这个函数
bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
{
//走到nullptr之后,判断k和black是否相等
if (nullptr == pRoot)
{
if (k != blackCount)
{
cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
return false;
}
return true;
}
//统计黑色节点的个数
if (BLACK == pRoot->_col)
k++;
//检测当前节点与其双亲是否都为红色
if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
{
cout << "违反性质三:存在连在一起的红色节点" << endl;
return false;
}
return _IsValidRBTree(pRoot->_left, k, blackCount) &&
_IsValidRBTree(pRoot->_right, k, blackCount);
}
通过递归的方式将每条支路都走了一遍和基准值比较,并且将红黑树的性质全都验证了。
测试2:
void TestRBTree2()
{
const size_t N = 1024 * 1024 * 10;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; ++i)
{
//v.push_back(rand());
v.push_back(i);
}
RBTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
}
//t.levelOrder();
//cout << endl;
cout << "是否为红黑树?" << t.IsBalanceTree() << endl;
t.Height();
//t.InOrder();
}