红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
那么红黑树是如何保证根到叶子节点的最长路径不会超过最短路径的两倍?
那么我们假设红黑树中根到叶子节点包含的黑色节点为N,那么最短路径就是全部为黑色节点,即长度为N
那么最长路径就是一黑一红,当红色和黑色节点的数目相同就是2N,因此,根到叶子节点的最长路径不会超过最短路径的两倍
红黑树节点的定义
我们这里用K V的模型,为方便后续的旋转操作
// 枚举定义节点颜色
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):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}};
这里我们给插入的节点定义为红色,是为了不违反规则4,规则4相较于规则3无异于更为严格,应为我们如果插入黑色节点一定会破坏规则4,如果插入红色节点只是可能破坏规则3,规则3可以理解为不能有连续的红色节点,因为父节点可能为黑色
总的来说就是:
· 插入黑色一定破坏规则4,必须调整红黑树
· 插入红色可能破坏规则3,可能调整红黑树
红黑树的插入
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1. 按照二叉搜索的树规则插入新节点
2. 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
情况一:g为黑,p为红,u存在且为红
为避免出现连续的红色节点,我们可将父亲节点变为黑,同时将祖父变为红,叔叔变为黑保证每条路径的黑色节点相同
但是祖父可能是根节点可能是某个树的子树,所以保险起见再把祖父变为黑
注意:cur不管是parent左边还是右边无所谓
抽象图:
情况二:p为红,g为黑,叔叔存在且为黑
这种情况一定是在情况一继续往上调整的过程中出现的,即这种情况下的cur结点一定不是新插入的结点,而是上一次情况一调整过程中的祖父结点,如下图:
我们将路径中祖父结点之上黑色结点的数目设为x xx,将叔叔结点之下黑色结点的数目设为y yy,则在插入结点前,图示两条路径黑色结点的数目分别为x + 1 x+1x+1 和 x + 2 + y x+2+yx+2+y,很明显x + 2 + y x+2+yx+2+y 是一定大于 x + 1 x+1x+1的,因此在插入结点前就不满足红黑树的要求了,所以说叔叔结点存在且为黑这种情况,一定是由情况一往上调整过程中才会出现的一种情况。
出现叔叔存在且为黑时,单纯使用变色已经无法处理了,这时我们需要进行旋转处理。若祖孙三代的关系是直线(cur、parent、grandfather这三个结点为一条直线),则我们需要先进行单旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根结点是黑色的,因此无需继续往上进行处理。
当直线关系为,parent是grandfather的右孩子,cur是parent的右孩子时,就需要先进行左单旋操作,再进行颜色调整。
若祖孙三代的关系是折现(cur、parent、grandfather这三个结点为一条折现),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。
当折现关系为,parent是grandfather的右孩子,cur是parent的左孩子时,就需要先进行右左双旋操作,再进行颜色调整。
情况三:p为红,g为黑,叔叔不存在
实际上情况二与情况三是一样的,如图:
调整情况完全相同
代码如下:
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->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}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;// parent的颜色是黑色也结束while (parent && parent->_col == RED){// 关键看叔叔Node* grandfather = parent->_parent;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;}else // 叔叔不存在,或存在且为黑{if (cur == parent->_left){// g// p u// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;// 叔叔存在且为红,变色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色// g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}
红黑树的验证
红黑树是特殊的二叉树,因此,我们可以用中序遍历来判断是否满足二叉树的性质
void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}
然后验证是否满足红黑树的性质
bool IsBalance(){if (_root->_col == RED){return false;}int refnum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refnum;}cur = cur->_left;}return Check(_root, 0, refnum);}bool Check(Node* root, int blacknum, const int refnum){if (root == nullptr){if (blacknum != refnum){cout << "存在节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续红色的节点" << endl;return false;}if (root->_col == BLACK){blacknum++;}return Check(root->_left, blacknum, refnum)&& Check(root->_right, blacknum, refnum);}
红黑树的查找:
红黑树的查找函数与二叉搜索树的查找方式一模一样,逻辑如下:
若树为空树,则查找失败,返回nullptr。
若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
若key值等于当前结点的值,则查找成功,返回对应结点。
Node* Find(const K& kv)
{Node* cur = _root;while (cur){if (cur->_first > kv){cur = cur->_left;}else if (cur->_first < kv){cur = cur->_right;}else{return cur;}}return nullptr;
}
红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
附上完整代码:
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):_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* parent = nullptr;Node* cur = _root;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->_col = RED; // 新增节点给定红色if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// parent的颜色是黑色也结束while (parent && parent->_col == RED){// 关键看叔叔Node* grandfather = parent->_parent;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;}else // 叔叔不存在,或存在且为黑{if (cur == parent->_left){// g// p u// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;// 叔叔存在且为红,变色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色// g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}Node* Find(const K& kv)
{Node* cur = _root;while (cur){if (cur->_first > kv){cur = cur->_left;}else if (cur->_first < kv){cur = cur->_right;}else{return cur;}}return nullptr;
}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_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 RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root->_col == RED){return false;}int refnum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refnum;}cur = cur->_left;}return Check(_root, 0, refnum);}private:bool Check(Node* root, int blacknum, const int refnum){if (root == nullptr){if (blacknum != refnum){cout << "存在节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续红色的节点" << endl;return false;}if (root->_col == BLACK){blacknum++;}return Check(root->_left, blacknum, refnum)&& Check(root->_right, blacknum, refnum);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}Node* _root = nullptr;
};