【C++】map与set底层结构——红黑树
目录
前言
1.红黑树
1.1红黑树的概念
1.2红黑树的规则
2.红黑树的实现
2.1红黑树的结构
2.2红黑树的插入
2.2.1变色
2.2.2单旋+变色
2.2.3双旋+变色
2.2.4插入代码
2.3红黑树的查找
2.4红黑树的验证
3.测试代码
前言
map与set作为STL中自带的搜索结构,其底层依托于红黑树而实现。本文将详细讲解红黑树的概念及其实现逻辑,其中部分基础逻辑(如旋转)可参考AVL树的实现。更多C++内容可前往>| C++专栏 |<
1.红黑树
1.1红黑树的概念
红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。
1.2红黑树的规则
1.每个节点不是红色就是黑色
2.根节点是黑色的
3.如果⼀个节点是红⾊的,则它的两个孩⼦节点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊节点。
4.对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色节点
2.红黑树的实现
2.1红黑树的结构
红黑树整体还是搜索树的一种,但每个节点除了存储键值、左子节点和右子节点指针外,还需要额外存储一个颜色属性。这里的颜色属性可用枚举值来表示。
#pragma once
#include<iostream>
using namespace std;enum Color
{RED,BLACK
};template<class K, class V>
class RBTreeNode
{pair<K, V> _kv;RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;Color _col;RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){ }
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:private:Node* _root = nullptr;
};
2.2红黑树的插入
1.红黑树插入情况分析
1.大体插入按二叉搜索树的规则插入,插入完看是否符合红黑树的规则
2.当为空树插入时,新增节点为黑色节点;如果是非空树插入,新增节点必须是红色节点(若为黑色节点,则破坏规则4,更难处理)
3.⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束
4.⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3,此时须进行特殊处理
对情况4进行进一步分析:
例如在该树中插入一个新节点,其中cur表示新增节点,parent表示新增节点的父亲,grandpa表示新增节点的父亲的父亲,uncle表示parent的相邻节点。如何parent为黑则插入结束,如果parent为红则grandpa必定为黑,此时再将这棵树单独拿出来:
此时根据分析cur为红,parent为红,则grandpa必为黑,最终问题情况就要由uncle颜色来确定:
2.2.1变色
情况1:cur为红,parent为红,grandpa为黑,uncle存在且为红。
此时的情况就与上图的情况相同,这时新增节点为红色,那么只需要将parent节点与uncle节点都变为黑,grandpa变为红即可。
即将grandpa的黑色分散到两个子路径中,同时满足规则3与规则4。但要注意的是若此时grandpa为根节点则需将其再变为黑色;若不为根,则继续向上更新。
此为具体图分析,下面展示抽象图。
此时再回到更新后的树:
可以看到仍然存在违反规则3的情况,下面继续分析
情况2:cur为红,parent为红,grandpa为黑,uncle不存在或存在且为黑。
若uncle不存在则cur必为新增节点;若uncle存在且为黑则cur必不为新增节点(如上图),一定是通过变色变为红色。
此时必须要将parent变为黑才能解决连续红色的问题,但因uncle不存在或为黑,无法单纯通过变色解决问题,此时我们可以根据AVL树的旋转情况以grandpa为根进行旋转。
2.2.2单旋+变色
如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可。
如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可。
2.2.3双旋+变色
如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变⿊,g变红即可。
如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变⿊,g变红即可。
2.2.4插入代码
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){parent = cur;if (kv.first < cur->_kv.first){cur = cur->_left;}else if (kv.first > cur->_kv.first){cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_col = RED;if (kv.first < parent->_kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;while (parent && parent->_col == RED){Node* grandpa = parent->_parent;if (parent == grandpa->_left){Node* uncle = grandpa->_right;//uncle存在且为红if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandpa->_col = RED;cur = grandpa;parent = cur->_parent;}//uncle不存在或uncle存在且为黑else{if (cur == parent->_left){rotateR(grandpa);parent->_col = BLACK;grandpa->_col = RED;}else{rotateLR(grandpa);parent->_col = BLACK;grandpa->_col = RED;}}}else{Node* uncle = grandpa->_left;//uncle存在且为红if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandpa->_col = RED;cur = grandpa;parent = cur->_parent;}//uncle不存在或uncle存在且为黑else{if (cur == parent->_right){rotateL(grandpa);parent->_col = BLACK;grandpa->_col = RED;}else{rotateRL(grandpa);parent->_col = BLACK;grandpa->_col = RED;}}}}_root->_col = BLACK;//确保根节点一直为黑return true;
}
2.3红黑树的查找
红黑树查找逻辑与二叉搜索树查找逻辑相同:
//查找
Node* find(const pair<K, V>& x)
{Node* cur = _root;while (cur){if (x.first < cur->_kv.first){cur = cur->_left;}else if (x.first > cur->_kv.first){cur = cur->_right;}else{return cur;}}return nullptr;
}
2.4红黑树的验证
这⾥获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。
红黑树4点规则检查
1. 规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。
2. 规则2直接检查根即可
3. 规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。
4. 规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。
代码如下:
bool Check(Node* cur, int blackNum, const int blackNumRef)
{if (cur == nullptr){if (blackNum != blackNumRef){cout << "黑色节点的数量不相等" << endl;return false;}return true;}if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED){cout << cur->_kv.first << "->" << "连续的红色节点" << endl;return false;}if (cur->_col == BLACK)++blackNum;return Check(cur->_left, blackNum, blackNumRef)&& Check(cur->_right, blackNum, blackNumRef);
}//验证
bool isBalance()
{if (_root == nullptr)return true;if (_root->_col == RED)return false;// 黑色节点数量参考值Node* leftMost = _root;int blackRef = 0;while (leftMost){if (leftMost->_col == BLACK)++blackRef;leftMost = leftMost->_left;}return Check(_root, 0, blackRef);
}
3.测试代码
-
#include"RBTree.h"
#include<iostream>
using namespace std;void TestRBTree1()
{RBTree<int, int> t;// 常规的测试用例//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的带有双旋场景的测试用例int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){if (e == 14){int x = 0;}t.insert({ e, e });//t.InOrder();//cout << "Insert:" << e << "->" << t.IsBalanceTree() << endl;}t.inOrder();cout << t.isBalance() << endl;
}int main()
{TestRBTree1();return 0;
}