平衡二叉查找树有很多,但是我们一提到平衡二叉查找树,常提及的就是红黑树,它的“出镜率”甚至要高于平衡二叉查找树。
红黑树是一种相对平衡的二叉查找树,不符合严格意义上平衡二叉查找树的定义。
目录
红黑树的插入
红黑树的验证
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 任何上下相邻的节点不能同时为红色 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的所有路径,都包含相同数目的黑色结点
- 每个叶子结点都是黑色的空节点,也就是说,叶子节点不存储数据。
满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍。
红黑树的性能分析
假设每条路径上的黑节点是N,最短的路径全是黑节点,最长的路径节点一个黑节点一个红节点相间 ,N<=任意路径长度<=2N。
平衡二叉查找树的提出是为了解决二叉查找树因为动态更新导致的性能退化问题。因此,“平衡”可以等价为性能不退化,“近似平衡”就等价为性能退化不太严重。二叉查找树的很多操作的时间复杂度与树的高度成正比。一颗极其平衡的二叉树(满二叉树或完全二叉树)的高度大约是log n,如果要证明红黑树是近似平衡的,只需要证明红黑树的高度近似于log n(比如同一量级)。
证明:
红黑树的最长路径不会超过2log n,也就是说,红黑树的高度不会超过2log n。红黑树只比高度平衡的AVL树高了一倍,因此损失的性能并不多。而相对于AVL树,红黑树维护成本更低,因此,性能并不比AVL树差。
所以为什么红黑树如此受欢迎呢?
AVL树是一种高度平衡的二叉树,查找数据的效率非常高,但是,AVL树为了维持这种高度的平衡,需要付出更多的代价。为了维持平衡性,每次插入、删除数据都要对树中节点的分布做调整,操作复杂、耗时。
红黑树只做到了近似平衡,并没有做到严格定义上的平衡,因此,维护平衡性的成本比AVL树要低,但性能又损失不大。对于工程应用,我们更倾向于维护成本和性能相对折中的红黑树。更加重要的一点是,大部分编程语言提供了封装了红黑树实现的类,我们直接拿来用即可,不需要从零开始实现,大大节省了开发时间。红黑树是一种近似平衡的二叉查找树。 它是为了解决二叉查找树动态数据更新导致的性能退化问题而创造的。红黑树的高度近似于logn,插入、删除和查找操作的时间复杂度都是O(logn)。
红黑树节点的定义
// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template
struct RBTreeNode
{
RBTreeNode(const T& data = T(),Color color = RED)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _color(color)
{}
RBTreeNode* _pLeft; // 节点的左孩子
RBTreeNode* _pRight; // 节点的右孩子
RBTreeNode* _pParent; // 节点的双亲
T_data; // 节点的值域
Color _color; // 节点的颜色
};
在插入节点时,我们将节点的默认颜色给成红色的,因为每条路径上黑色节点相同,如果将节点给成黑色的,就会影响每一条路径,将新节点给成红色如果不符合规则,只需要调整这一条或者相邻路径,将新节点给成红色比将新节点给成黑色代价要小得多。
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1. 按照二叉搜索的树规则插入新节点
2.检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了红黑树的性质,不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
cur节点不一定是新增节点,有可能是调整之后指向的节点。
情况一: cur为红,p为红,g为黑,u存在且为红
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
u的情况有两种
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质∶每条路径黑色节点个数相同。
2.如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。
解决方式: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
代码实现:
bool Insert(const T& data){if (_root == nullptr){_root = new Node(data);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (data > cur->_data){parent = cur;cur = cur->_right}else if (data < >> cur->_data){parent = cur;cur = cur->_left}else {return false;}}//插入节点cur = new Node(data);cur->_col(RED);if (parent->data < data){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//控制平衡while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = parent->_right;//1.uncle存在且为红if (uncle && uncle->_col == RED){//变色+继续向上处理parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//2.uncle不存在/uncle存在且为黑{// g// p// c// g// p// cif (cur == parent->_left){//右旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}if (cur == parent->_right){//先左旋再右旋RotateL(parent);RotateR(grandfather);parent->_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 // 2 + 3、uncle不存在/ 存在且为黑{// g // p// c// g// p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}
红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2. 检测其是否满足红黑树的性质
bool IsBalance(){if (_root && _root->_col == RED){cout << "根节点不是黑色" << endl;return false;}// 最左路径黑色节点数量做基准值int banchmark = 0;Node* left = _root;while (left){if (left->_col == BLACK)++banchmark;left = left->_left;}int blackNum = 0;return _IsBalance(_root, banchmark, blackNum);}bool _IsBalance(Node* root, int banchmark, int blackNum){if (root == nullptr){if (banchmark != blackNum){cout << "存在路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "出现连续红色节点" << endl;return false;}if (root->_col == BLACK){++blackNum;}return _IsBalance(root->_left, banchmark, blackNum)&& _IsBalance(root->_right, banchmark, blackNum);}