一、红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的 (即不能出现两个连续的红色结点)
- 对于每个结点,从该结点到其所有后代叶结点的简单路径(从根节点到空结点)上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,也被叫做NIL)
下图是一个红黑树
根据红黑树的性质:我们能得到这样一个结论---最长路径不会超过最短路径的两倍,为什么呢?
理由如下:因为每条路径上黑色节点的个数要相同,所以最短的路径上的点均为黑色结点,同时因为不能出现红色结点相邻的情况,所以最长路径上的结点颜色只能是黑红相间,故得出上诉结论
二、红黑树结点定义
enum Colour //这里用的枚举类型,也可以用其他的类型,只要能代表红黑两种颜色就行,如true/false
{BLACK,RED
};template<class K,class V>
struct RBTreeNode {RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V>_kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};
思考:为什么要将结点颜色默认设置为红色???
理由如下:由于每条路径上的黑色节点的个数要保持相同,如果我们插入的结点的颜色为黑色,那么必然该节点所在路径的黑色节点的个数要增加,就会导致这颗树的其他所有路径都需要多一个黑色节点,影响范围太大,而如果插入的结点颜色为红色,我们只要关心它所在子树的情况就行,具体看下面的插入操作。故每个新插入结点颜色都默认为红色
三、红黑树插入结点
template <class K,class V>
class 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* cur = _root;Node* parent = nullptr;while (cur) {if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//调整红黑树//...}
private:Node* _root = nullptr;
};
2.检查新节点插入后,红黑树的性质有没有被破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论 (p-parent g-grandfather u-uncle)
情况一:cur为红,p为红,g为黑,u存在且为红
在这种情况下,我们只要改变结点的颜色就能保持红黑树的性质。
(注意:这种情况下,不用关心cur所在的位置)
情况二:cur为红,p为红,g为黑,u不存在或为黑色
这里就无法通过改变p、g、u的颜色来保持红黑树的性质(读者可以去手玩尝试一下),需要进行旋转+变色才行,那么看到这种形状的图形,我们就应该想到AVL树中的单旋和双旋,上面的情况很明显对应右单旋,旋转之后如下图
如何改变颜色使得红黑树的性质保持不变?
1.uncle为空,显而易见,将g变为红,p变为黑(可能有人会觉得将cur变成黑不是也行嘛?但如果p为红,且它是子树,那么还需要向上调整,会很麻烦,但如果p为黑色,那么它本身既符合红黑树性质,也并不会对其他的子树产生影响,直接就一步到位了)
2.uncle为黑色,(这种情况下,cur不可能是新插入的结点,只能是情况一向上调整得到的),这里就要分析a,b,c,d,e这几颗子树中黑色节点的个数,显然a,b,c的黑色结点个数相同,d和e的黑色结点比abc少一个,所以将p变为黑色,g变为红色,就能保持红黑树的性质(至于为什么不选择将g变成红色,理由同上)
具体的旋转和AVL树一样,只是红黑树旋转后需要变色,AVL树旋转后需要调整平衡因子,不了解的,可以看我写过的AVL树
这里,我们还要考虑cur所在的位置,共四种情况,第一种情况就是上面所讲的,剩下三种的旋转+变色,留给读者思考【旋转不会的可以去看我之前写的AVL树】
总结:红黑树的结点的插入首先看父节点是否为黑色(插入结点为根的情况要特别判断),如果为黑,不用处理,如果为红,关键看uncle结点,它决定了我们是否需要旋转,如果是红色,则只需要变色,如果是黑色/不存在,我们需要根据cur所在的位置选择合适的旋转方式,并对旋转之后的结点进行变色
代码如下---附带检查检查红黑树是否正确的函数
enum Colour {BLACK,RED
};template<class K,class V>
struct RBTreeNode {RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V>_kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template <class K,class V>
class 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* cur = _root;Node* parent = nullptr;while (cur) {if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//父节点为黑,不用处理//为红需要调整while (parent && parent->_col == RED){// g// p u//cNode* grandparent = parent->_parent;if (grandparent->_left == parent){Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED)//情况一{grandparent->_col = RED;parent->_col = uncle->_col = BLACK;cur = grandparent;parent = cur->_parent;}else//情况二{if (parent->_left == cur)//单旋{RotateR(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else//双旋{RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}else{// g// u p// c cNode* uncle = grandparent->_left;if (uncle && uncle->_col == RED){//变色grandparent->_col = RED;parent->_col = uncle->_col = BLACK;//向上走cur = grandparent;parent = cur->_parent;}else{if (parent->_right == cur){RotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{RotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pParent = parent->_parent;subR->_left = parent;parent->_right = subRL;parent->_parent = subR;if (subRL)//注意h==0的情况subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{subR->_parent = pParent;if (pParent->_left == parent){pParent->_left = subR;}else{pParent->_right = subR;}}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pParent = parent->_parent;subL->_right = parent;parent->_left = subLR;parent->_parent = subL;if (subLR)//注意h==0的情况subLR->_parent = parent;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{subL->_parent = pParent;if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}}}void InOrder(){_InOrder(_root);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}bool Isbalance(){return _Isbalance(_root);}//检查是否出现两个连续的红色节点+是否路径上的黑色节点的个数是否相同bool check(Node* root,int blacknum){if (root == nullptr){cout << blacknum << " ";return true;}if (root->_col == RED && root->_parent->_col == RED)return false;if (root->_col == BLACK)blacknum++;return check(root->_left, blacknum) && check(root->_right, blacknum);}bool _Isbalance(Node* root){if (root == nullptr)return true;if (root->_col == RED){cout <<"出现两个连续的红色节点:"<< root->_kv.first << endl;return false;}return check(root->_left, 1) && check(root->_right, 1);}
private:Node* _root = nullptr;
};