1.什么叫红黑树?
红黑树是一棵二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子简单路径上的颜色来约束,红黑树保证最长路径不超过最短路径的两倍,因而近似于平衡。
2.红黑树的性质
1. 每个节点,不是红色就是黑色的
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个子节点是黑色的(没有连续的红色结点)
4. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。(每条路径的黑色结点个数相等)
5. 每个叶子节点都是黑色的(这里的叶子节点是指的NIL节点(空节点))
3.红黑树为什么通过颜色的约束能够保证最长路径不超过最短路径的两倍?
4.红黑树的结点的定义
enum Colour
{
RED,
BLACK
};
template<typename K,typename V>
struct RBTreeNode
{
//结点
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
//颜色
Colour _col;
//KV
K _key;
V _value;
RBTreeNode(const K& key, const V& value)
:_key(key)
, _value(value)
, _left(NULL)
, _right(NULL)
, _parent(NULL)
, _col(RED) //把新增结点置成红色为了保持每条路径的黑色结点个数不变
{}
};
为什么把新增结点的颜色设置为红色而不是黑色呢?
因为设置为红色,比较容易维护这棵树,设置为黑色,会导致当前这条路径多了一个黑色结点,为了满足每条路径的黑色结点个数相同,需要维护多条路径。
5.红黑树插入的几种情况
注:由于p结点(父结点)可能为g结点(祖父结点)的左孩子或者右孩子,下面的作图以左孩子为模板,右孩子与左孩子类似,只是在某些需要旋转的情况下不一样。
(1)插入结点为根节点(只有一个结点的情况),将结点颜色置成黑色即可
(2)插入结点的父结点或调整结点的父结点为黑色结点(不需要调整)
(3)插入结点的父结点或向上调整的结点父结点为红色之——uncle结点存在为红色
(4)插入结点的父结点为红色之——uncle结点不存在
(5)插入结点的父结点为红色之——uncle结点存在为黑色
6.红黑树插入实现代码
template<class K,class V> class RBTree { typedef RBTreeNode<K, V> Node; public: RBTree() :_root(NULL) {} bool Insert(const K& key, const V& value) { //1.插入结点为根节点,必须为黑色 if (_root == NULL) { _root = new Node(key, value); _root->_col = BLACK; return true; } Node* cur = _root; Node* parent = NULL; while (cur != NULL) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { return false; } } cur = new Node(key, value); if (key > parent->_key) parent->_right = cur; else parent->_left = cur; cur->_parent = parent; while (parent != NULL) { Node* pparent = parent->_parent; Node* uncle = NULL; //2.如果插入节点或者调整结点的父结点为黑色结点,增加一个红色结点,对每条路径的黑色结点个数没影响 if (parent->_col == BLACK) { break; } //判断叔叔结点是否存在,存在是在左还是右? if (pparent != NULL) { if (pparent->_right == parent) uncle = pparent->_left; else uncle = pparent->_right; } //3.插入结点或调整结点父结点为红色且叔叔结点为红色 if (uncle != NULL&&uncle->_col == RED) { parent->_col = BLACK; uncle->_col = BLACK; if (pparent == _root) return true; pparent->_col = RED; //继续向上调整 //假如调整到根结点,只需将根结点的颜色置为黑色即可 if (parent == _root) { parent->_col = BLACK; return true; } cur = parent->_parent; parent = cur->_parent; } //4.插入结点或调整结点父结点为红色且叔叔结点不存在或 // 插入结点或调整结点父结点为红色且叔叔结点存在为黑色二者逻辑一样。 else //剩下的情况 (uncle == NULL || uncle->_col == BLACK) { //判断父结点在左还是在有 if (pparent->_left == parent) { //左--左 if (parent->_left == cur) { _RotateR(pparent); pparent->_col = RED; parent->_col = BLACK; if (pparent == _root) { _root = parent; } } //左--右 else { _RotateL(parent); _RotateR(pparent); cur->_col = BLACK; parent->_col = RED; pparent->_col = RED; if (pparent == _root) _root = cur; } } //父结点在右 else { //右--右 if (parent->_right == cur) { _RotateL(pparent); pparent->_col = RED; parent->_col = BLACK; if (pparent == _root) _root = parent; } //右--左 else { _RotateR(parent); _RotateL(pparent); parent->_col = RED; pparent->_col = RED; cur->_col = BLACK; if (pparent == _root) _root = parent; } } break; } } return true; } void InOrder() { _InOrder(_root); cout << endl; } protected: void _InOrder(Node* root) { if (root == NULL) return; _InOrder(root->_left); cout << root->_key << " "; _InOrder(root->_right); } void _RotateR(Node* parent) { Node* SubL = parent->_left; Node* SubLR = SubL->_right; //链上SubLR parent->_left = SubLR; if (SubLR != NULL) SubLR->_parent = parent; Node* pparent = parent->_parent; //链上SubL SubL->_parent = pparent; if (pparent != NULL) { if (pparent->_left == parent) pparent->_left = SubL; else pparent->_right = SubL; } //链上parent parent->_parent = SubL; SubL->_right = parent; } void _RotateL(Node* parent) { Node* SubR = parent->_right; Node* SubRL= SubR->_left; //链上SubLR parent->_right= SubRL; if (SubRL != NULL) SubRL->_parent = parent; Node* pparent = parent->_parent; //链上SubL SubR->_parent = pparent; if (pparent != NULL) { if (pparent->_left == parent) pparent->_left = SubR; else pparent->_right = SubR; } //链上parent parent->_parent = SubR; SubR->_left = parent; } Node* _root; };
7.如何测试一棵树是不是红黑树?
(1)测试它的根节点是不是黑色
(2)测试是否有连续的红结点
(3)测试每条路径上的黑节点的数量是否相等?
以某一条路径(测试用例用的是左子树)上的黑色结点的个数为判断标准,如果有一条不满足相等,证明不是红黑树。
实现代码:
bool Isbalance() { //1.空树认为是RBTree if (_root == NULL) return true; //2.根结点非黑不是RBTree if (_root->_col == RED) return false; int count = 0; //以左子树上的黑色结点作为一个基准,如果一样就证明是,不一样就证明不是RBTree Node* cur = _root; while (cur != NULL) { if (cur->_col == BLACK) count++; cur = cur->_left; } //num用来标记每条路径上的黑色结点的个数 //count用来标记左子树上的黑色结点的个数 int num = 0; return _Isbalance(_root, count, num); } bool _Isbalance(Node* root, int count, int num) { if (root == NULL) return true; //3.连续红结点不是RBTree,root结点一定有父结点 if (root->_col == RED&&root->_parent->_col == RED) { cout << root->_key << " 有连续的红结点" << endl; return false; } if (root->_col == BLACK) num++; if (root->_left == NULL&&root->_right == NULL) { if (num != count) { cout << root->_key << " 黑色结点个数不一样" << endl; return false; } } return _Isbalance(root->_left, count, num) && _Isbalance(root->_right, count, num); }
8.测试用例
测试用例:
void TestRBTree() { int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; RBTree<int, int> t; for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) { t.Insert(a[i], i); } t.InOrder(); cout << "Isbalance---->" << t.Isbalance()<< endl; }
运行结果和测试用例红黑树模型:
9.红黑树和AVL树比较
(1)红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是O(lg(N))。
(2)红黑树的不追求完全平衡,保证最长路径不超过最短路径的2倍,相对而言,降低了旋转的要求,所以性能会优于AVL树,所以实际运用中红黑树更多。(效率差不多要求还低)。
注: