红黑树
- 一、红黑树的概念
- 二、红黑树的性质
- 三、红黑树结点的定义
- 四、红黑树的插入
- 五、红黑树的验证
- 七、红黑树的查找
- 七、红黑树与AVL树的比较
- 七、完整代码
- RBTree.h
一、红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
二、红黑树的性质
红黑树一共有五个性质:
1.每个结点不是红色就是黑色
2.根结点是黑色的
3.如果一个结点是红色的,则它的两个孩子结点是黑色的
4.对于每个结点,从该结点到其他所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
为什么满足上面的性质,红黑树就能保证:其最长路径中结点个数不会超过最短路径结点个数的两倍?
根据红黑树的性质3可以得出,红黑树当中不会出现连续的红色结点,而根据性质4又可以得出,从某一结点到其后代叶子结点的所有路径上包含的黑色结点的数目是相同的。
我们假设在红黑树中,从根到叶子的所有路径上包含的黑色结点的个数都是N个,那么最短路径就是全部由黑色结点构成的路径,即长度为N。
而最长可能路径就是由一黑一红结点构成的路径,该路径当中黑色结点与红色结点的数目相同,即长度为2N
因此,红黑树从根到叶子的最长可能路径不会超过最短可能路径的两倍。
三、红黑树结点的定义
这里直接实现K V模型的红黑树,为了方便后续的旋转操作,将红黑树的结点定义为三叉链结构,除此之外还新加入了一个成员变量,用于表示结点的颜色。
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){}
};
这里使用枚举来定义结点的颜色,这样可以增加代码的可读性和可维护性
enum Colour
{RED,BLACK,
};
为什么构造结点时,默认将结点的颜色设置为红色?
当我们向红黑树插入结点时,若我们插入的是黑色结点,那么插入路径上黑色结点的数目就比其他路径上黑色结点的数目多了一个,即破坏了红黑树的性质4,此时我们就需要对红黑树进行调整。
若我们插入红黑树的结点时红色的,此时如果其父结点也是红色的,那么表明出现了连续的红色结点,即破坏了红黑树的性质3,此时我们需要对红黑树进行调整,但如果其父结点是黑色的,那我们就无需对红黑树进行调整,插入后仍满足红黑树的要求。
总结:
- 插入黑色结点,一定破坏红黑树的性质4,必须对红黑树进行调整
- 插入红色结点,可能破坏红黑树的性质3,可能对红黑树进行调整
四、红黑树的插入
红黑树插入结点的逻辑分为三步
1.按二叉搜索树的插入方法,找到待插入位置。
2.将待插入结点插入到树中。
3.若插入结点的父结点是红色的,则需要对红黑树进行调整。
其中前两步与二叉搜索树插入结点时的逻辑相同,红黑树的关键在于第三步对红黑树的调整。
实际上插入结点后并不是一定会对红黑树进行调整,若插入结点的父结点是黑色的,那么我们就不用对红黑树进行调整,因为本次结点的插入并不会破坏红黑树的五点性质。
只有当插入结点的父结点是红色时才需要对红黑树进行调整,因为我们默认插入的结点就是红色的,如果插入结点的父结点也是红色的,那么此时就出现了连续的红色结点,因此需要对红黑树进行调整。因为插入结点的父结点是红色的,说明父结点不是根结点(根结点是黑色的),因此插入结点的祖父结点(父结点的父结点)就一定存在。
红黑树调整时具体应该如何调整,主要是看插入结点的叔叔(插入结点的父结点的兄弟结点),根据插入结点叔叔的不同,可将红黑树的调整分为三种情况。
情况一:插入结点的叔叔存在,且叔叔的颜色是红色
此时为了避免出现连续的红色结点,我们可以将父结点变黑,但为了保持每条路径黑色结点的数目不变,因此我们还需要将祖父结点变红,再将叔叔变黑。这样一来既保持了每条路径黑色结点的数目不变,也解决了连续红色结点的问题。
但是调整还没有结束,因为此时祖父结点变成了红色,如果祖父结点是根节点,那我们直接再将祖父结点变成黑色即可,此时相当于每条路径黑色结点的数目都增加了一个。
但如果祖父结点不是根结点的话,我们就需要将祖父结点当作新插入的结点,再判断其父结点是否为红色,若其父结点也是红色,那么又需要根据其叔叔的不同,进行不同的调整操作
情况二:插入结点的叔叔存在,且叔叔的颜色是黑色
这种情况一定是在情况一继续往上调整的过程中出现的,即这种情况下的cur结点一定不是新插入的结点,而是上一次情况一调整过程中的祖父结点。如果叔叔结点存在,则其一定是黑色的,那么cur结点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur结点的颜色由黑色改成红色。
注意
1.从根结点一直走到空位置就算一条路径,而不是从根结点走到左右结点均为空的叶子结点时才算一条路径。
2.情况二和情况三均需要进行旋转处理,旋转处理后无需往上进行调整。
当成直线关系时,p为g的左孩子,cur为p的左孩子,则进行右单旋转操作,再进行颜色调整。
当成折线关系时,p为g的左孩子,cur为p的右孩子,则进行左右双旋,再进行颜色调整。
情况三:插入结点的叔叔不存在
在这种情况下的cur结点一定是新插入的结点,而不可能是由情况一变化而来的,因为叔叔不存在说明在p的下面不可能再挂黑色结点了。
当成直线关系时,p为g的左孩子,cur为p的左孩子时,就需要进行左单旋操作,再进行颜色调整。
当出现折线关系时,p为g的左孩子,cur为p的右孩子时,就需要进行左右双旋操作,再进行颜色调整。
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->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;//情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续往上调整cur = grandfather;parent = cur->_parent;}else //情况2+3: u不存在/u存在且为黑,旋转+变色(p变黑,g变红){// g// p u// cif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){// g// u p// cNode* uncle = grandfather->_left;//情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续往上调整cur = grandfather;parent = cur->_parent;}else //情况2+3:u不存在/u存在且为黑,旋转+变色{// g// u p// cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = 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 InOrder(){_InOrder(_root);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
但中序有序只能证明是二叉搜索树,要证明二叉树是红黑树还需验证该二叉树是否满足红黑树的性质。
bool IsBalance(){if (_root && _root->_col == RED){cout << "根结点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}//连续红色结点return _Check(_root,0,benchmark);}int Height(){return _Height(_root);}int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _Check(Node* root,int blackNum,int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色结点的数量不相等" << endl;return false;}//cout << blackNum << endl;return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col==RED){cout << "存在连续的红色结点" << endl;return false;}return _Check(root->_left,blackNum,benchmark)&& _Check(root->_right,blackNum,benchmark);}
七、红黑树的查找
红黑树的查找函数与二叉搜索树的查找方式一模一样
1.若树为空树,则查找失败,返回nullptr
2.若key值小于当前结点的值,则应该在该结点的左子树当中进行查找
3.若key值大于当前结点的值,则应该在该结点的右子树当中进行查找
4.若key值等于当前结点的值,则查找成功,返回对应结点
Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}
七、红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
七、完整代码
RBTree.h
#pragma onceenum 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:~RBTree(){_Destroy(_root);_root = nullptr;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}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->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;//情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续往上调整cur = grandfather;parent = cur->_parent;}else //情况2+3: u不存在/u存在且为黑,旋转+变色(p变黑,g变红){// g// p u// cif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){// g// u p// cNode* uncle = grandfather->_left;//情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续往上调整cur = grandfather;parent = cur->_parent;}else //情况2+3:u不存在/u存在且为黑,旋转+变色{// g// u p// cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void InOrder(){_InOrder(_root);}bool IsBalance(){if (_root && _root->_col == RED){cout << "根结点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}//连续红色结点return _Check(_root,0,benchmark);}int Height(){return _Height(_root);}private:void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _Check(Node* root,int blackNum,int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色结点的数量不相等" << endl;return false;}//cout << blackNum << endl;return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col==RED){cout << "存在连续的红色结点" << endl;return false;}return _Check(root->_left,blackNum,benchmark)&& _Check(root->_right,blackNum,benchmark);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = 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 _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}Node* _root=nullptr;
};void Test_RBTree1()
{//int a[] = { 16,3,7,11,9,26,18,14,15 };int a[] = { 4,2,6,1,3,5,15,7,16,14 };RBTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));//cout << e << "插入:" << t1.IsBalance() << endl;}t1.InOrder();cout << t1.IsBalance() << endl;
}void Test_RBTree2()
{srand(time(0));const size_t N = 100000;RBTree<int, int> t;for (size_t i = 0; i < N; ++i){size_t x = rand()+i;t.Insert(make_pair(x, x));//cout << t.IsBalance() << endl;}//t.InOrder();cout << t.IsBalance() << endl;cout << t.Height() << endl;
}