红黑树
红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
红黑树节点的定义
// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template <class T>
class RBTreeNode
{
T m_data;
RBTreeNode<T>* m_left;
RBTreeNode<T>* m_right;
RBTreeNode<T>* m_parent;
Color m_color;
public:
RBTreeNode(const T& val = T(), Color color = RED) :
m_left(nullptr),
m_right(nullptr),
m_parent(nullptr),
m_data(val),
m_color(color)
{}
template <class T>
friend class RBTree;
};
红黑树结构
为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 m_parent 域指向红黑树的根节点,m_left域指向红黑树中最小的节点,m_right域指向红黑树中最大的节点。
红黑树的插入操作
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
- 按照二叉搜索的树规则插入新节点
template<class T>
class RBTree
{
//......
bool Insert(const T& data)
{
RBTreeNode& pRoot = GetRoot();
if (nullptr == pRoot)
{
pRoot = new Node(data, BLACK);
// 根的双亲为头节点
pRoot->_pParent = _pHead;
_pHead->_pParent = pRoot;
}
else
{
// 1. 按照二叉搜索的树方式插入新节点
// 2. 检测新节点插入后,红黑树的性质是否造到破坏,
// 若满足直接退出,否则对红黑树进行旋转着色处理
}
// 根节点的颜色可能被修改,将其改回黑色
pRoot->_color = BLACK;
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
private:
PNode& GetRoot() {
return _pHead->_pParent;
}
// 获取红黑树中最小节点,即最左侧节点
PNode LeftMost();
// 获取红黑树中最大节点,即最右侧节点
PNode RightMost();
private:
PNode _pHead;
};
- 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
- 情况一: cur为红,p为红,g为黑,u存在且为红
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
- 情况二: cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红
- 情况三: cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
红黑树的检测:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
std::vector<T> InOrder()
{
std::stack<RBTreeNode<T>*> s;
std::vector<T> res;
RBTreeNode<T>* cur = m_head->m_parent;
while (cur || !s.empty())
{
for (; cur; cur = cur->m_left)
{
s.push(cur);
}
if (!s.empty())
{
cur = s.top();
res.push_back(cur->m_data);
s.pop();
cur = cur->m_right;
}
}
return res;
}
- 检测其是否满足红黑树的性质
bool IsValidRBTree()
{
RBTreeNode<T>* pRoot = getRoot();
// 空树也是红黑树
if (nullptr == pRoot)
return true;
// 检测根节点是否满足情况
if (BLACK != pRoot->m_color) {
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
// 获取任意一条路径中黑色节点的个数
size_t blackCount = 0;
RBTreeNode<T>* pCur = pRoot;
while (pCur)
{
if (BLACK == pCur->m_color)
blackCount++;
pCur = pCur->m_left;
}
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(RBTreeNode<T>* pRoot, size_t k, const size_t blackCount)
{
//走到null之后,判断k和black是否相等
if (nullptr == pRoot)
{
if (k != blackCount)
{
cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
return false;
}
return true;
}
// 统计黑色节点的个数
if (BLACK == pRoot->m_color)
k++;
// 检测当前节点与其双亲是否都为红色
RBTreeNode<T>* pParent = pRoot->m_parent;
if (pParent && RED == pParent->m_color && RED == pRoot->m_color) {
cout << "违反性质三:没有连在一起的红色节点" << endl;
return false;
}
return _IsValidRBTree(pRoot->m_left, k, blackCount) &&
_IsValidRBTree(pRoot->m_right, k, blackCount);
}
模拟实现红黑树的插入
.h文件
#pragma once
#include <stack>
#include <vector>
#include <iostream>
using namespace std;
namespace dg {
// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template <class T>
class RBTreeNode
{
T m_data;
RBTreeNode<T>* m_left;
RBTreeNode<T>* m_right;
RBTreeNode<T>* m_parent;
Color m_color;
public:
RBTreeNode(const T& val = T(), Color color = RED) :
m_left(nullptr),
m_right(nullptr),
m_parent(nullptr),
m_data(val),
m_color(color)
{}
template <class T>
friend class RBTree;
};
template <class T>
class RBTree
{
RBTreeNode<T>* m_head;
// 左旋
void lRound(RBTreeNode<T>* pre)
{
RBTreeNode<T>* parent = pre->m_parent;
RBTreeNode<T>* cur = pre->m_right;
cur->m_parent = parent;
if (parent != m_head)
{
if (parent->m_left == pre)
{
parent->m_left = cur;
}
else
{
parent->m_right = cur;
}
}
else
{
m_head->m_parent = cur;
cur->m_parent = m_head;
}
pre->m_right = cur->m_left;
if (cur->m_left)
{
cur->m_left->m_parent = pre;
}
cur->m_left = pre;
pre->m_parent = cur;
}
// 右旋
void rRound(RBTreeNode<T>* pre)
{
RBTreeNode<T>* parent = pre->m_parent;
RBTreeNode<T>* cur = pre->m_left;
cur->m_parent = parent;
if (parent != m_head)
{
if (parent->m_left == pre)
{
parent->m_left = cur;
}
else
{
parent->m_right = cur;
}
}
else
{
m_head->m_parent = cur;
cur->m_parent = m_head;
}
pre->m_left = cur->m_right;
if (cur->m_right)
{
cur->m_right->m_parent = pre;
}
cur->m_right = pre;
pre->m_parent = cur;
}
public:
RBTree()
{
m_head = new RBTreeNode <T>;
}
// 得到根节点(头节点父亲)
RBTreeNode<T>*& getRoot()
{
return m_head->m_parent;
}
// 返回树的最左端节点
RBTreeNode<T>* leftMost()
{
RBTreeNode<T>* cur = getRoot();
for (; cur->m_left; cur = cur->m_left);
return cur;
}
// 返回树的最右端节点
RBTreeNode<T>* rightMost()
{
RBTreeNode<T>* cur = getRoot();
for (; cur->m_right; cur = cur->m_right);
return cur;
}
bool insert(const T& val)
{
RBTreeNode<T>*& root = getRoot();
if (root)
{
// 插入节点
RBTreeNode<T>* cur = root;
RBTreeNode<T>* pre = nullptr;
while (cur)
{
if (val < cur->m_data)
{
pre = cur;
cur = cur->m_left;
}
else if (val > cur->m_data)
{
pre = cur;
cur = cur->m_right;
}
else
{
return false;
}
}
cur = new RBTreeNode<T>(val);
if (val < pre->m_data)
{
pre->m_left = cur;
}
else
{
pre->m_right = cur;
}
cur->m_parent = pre;
/*****************红黑树*****************/
if (pre->m_color == RED)
{
RBTreeNode<T>* grand = pre->m_parent;
RBTreeNode<T>* uncle;
if (pre == grand->m_left)
{
while (pre != m_head && pre->m_color == RED)
{
grand = pre->m_parent;
uncle = grand->m_right;
if (uncle && uncle->m_color == RED) //第二种情况
{
pre->m_color = BLACK;
uncle->m_color = BLACK;
grand->m_color = RED;
cur = grand;
pre = cur->m_parent;
}
else
{
if (cur == pre->m_right) //第三种情况转成第一种
{
lRound(pre);
RBTreeNode<T>* tmp;
tmp = pre;
pre = cur;
cur = tmp;
}
rRound(grand); //第一种情况
pre->m_color = BLACK;
grand->m_color = RED;
break;
}
}
}
else /*************上下是镜像**************/
{
while (pre != m_head && pre->m_color == RED)
{
grand = pre->m_parent;
uncle = grand->m_left;
if (uncle && uncle->m_color == RED)
{
pre->m_color = BLACK;
uncle->m_color = BLACK;
grand->m_color = RED;
cur = grand;
pre = cur->m_parent;
}
else
{
if (cur == pre->m_left)
{
rRound(pre);
RBTreeNode<T>* tmp;
tmp = pre;
pre = cur;
cur = tmp;
}
lRound(grand);
pre->m_color = BLACK;
grand->m_color = RED;
break;
}
}
}
}
//省略的else是第四种情况(pre->m_color == BLACK)
}
// 插入根节点(根节点不存在)
else
{
root = new RBTreeNode<T>(val, BLACK);
root->m_parent = m_head;
m_head->m_parent = root;
}
root->m_color = BLACK;
m_head->m_left = leftMost();
m_head->m_right = rightMost();
return true;
}
std::vector<T> InOrder()
{
std::stack<RBTreeNode<T>*> s;
std::vector<T> res;
RBTreeNode<T>* cur = m_head->m_parent;
while (cur || !s.empty())
{
for (; cur; cur = cur->m_left)
{
s.push(cur);
}
if (!s.empty())
{
cur = s.top();
res.push_back(cur->m_data);
s.pop();
cur = cur->m_right;
}
}
return res;
}
bool IsValidRBTree()
{
RBTreeNode<T>* pRoot = getRoot();
// 空树也是红黑树
if (nullptr == pRoot)
return true;
// 检测根节点是否满足情况
if (BLACK != pRoot->m_color) {
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
// 获取任意一条路径中黑色节点的个数
size_t blackCount = 0;
RBTreeNode<T>* pCur = pRoot;
while (pCur)
{
if (BLACK == pCur->m_color)
blackCount++;
pCur = pCur->m_left;
}
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(RBTreeNode<T>* pRoot, size_t k, const size_t blackCount)
{
//走到null之后,判断k和black是否相等
if (nullptr == pRoot)
{
if (k != blackCount)
{
cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
return false;
}
return true;
}
// 统计黑色节点的个数
if (BLACK == pRoot->m_color)
k++;
// 检测当前节点与其双亲是否都为红色
RBTreeNode<T>* pParent = pRoot->m_parent;
if (pParent && RED == pParent->m_color && RED == pRoot->m_color) {
cout << "违反性质三:没有连在一起的红色节点" << endl;
return false;
}
return _IsValidRBTree(pRoot->m_left, k, blackCount) &&
_IsValidRBTree(pRoot->m_right, k, blackCount);
}
};
};
主函数文件
#include "RBTree.h"
int main()
{
dg::RBTree<int> rbt;
rbt.insert(1);
rbt.insert(2);
rbt.insert(3);
rbt.insert(4);
rbt.insert(5);
rbt.insert(6);
rbt.insert(7);
rbt.insert(8);
rbt.insert(9);
rbt.insert(10);
if (rbt.IsValidRBTree()) {
cout << "满足红黑树的性质:";
}
else {
cout << "不满足红黑树的性质:";
}
vector<int> v = rbt.InOrder();
for (auto& i : v)
{
cout << i << ' ';
}
return 0;
}
代码生成图:
红黑树的迭代器
迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以前问题:
- begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点 (最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行- -操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:
- operator++()与operator–()
// 找迭代器的下一个节点,下一个节点肯定比其大
RBTreeNode<T>* increasement(RBTreeNode<T>* cur)
{
RBTreeNode<T>* tmp = cur;
if (cur->m_right)
{
// 右子树中最小的节点,即右子树中最左侧节点
tmp = cur->m_right;
for (; tmp->m_left; tmp = tmp->m_left);
}
else
{
// 右子树不存在,向上查找
tmp = tmp->m_parent;
for (; cur != tmp->m_left && tmp != m_head; tmp = tmp->m_parent)
{
cur = tmp;
}
// tmp 为根节点并且没右子树
if (tmp == m_head)
{
return nullptr;
}
}
return tmp;
}
// 获取迭代器指向节点的前一个节点
RBTreeNode<T>* decreasement(RBTreeNode<T>* cur)
{
RBTreeNode<T>* tmp = cur;
if (cur->m_left)
{
tmp = cur->m_left;
for (; tmp->m_right; tmp = tmp->m_right);
}
else
{
tmp = tmp->m_parent;
for (; cur != tmp->m_right && tmp != m_head; tmp = tmp->m_parent)
{
cur = tmp;
}
if (tmp == m_head)
{
return nullptr;
}
}
return tmp;
}
红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( log2N ),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,所以实际运用中红黑树更多。
如有不同见解,欢迎留言讨论~~