自己做鲜花网站怎么样收录查询
引入:为何要有红黑树树,AVL树有什么不足?
至今,若有用到二叉搜索树的场景,绝大多数用的是红黑树,而不是AVL树,因为:
一:红黑树的性质
2:没有连续的红色
3:每条路径都有相同个数的黑色节点
如图:
路径黑色个数为2,所以最短应该是纯黑路径长度为2,但不存在,最长是4个节点的一黑一红,也不存在
二:红黑树的实现
a:框架
和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){}
};//红黑树类
emplate<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;private:Node* _root = nullptr;};
对枚举的解释:
enum Colour
{RED,BLACK
};
①:这是一个简单的枚举定义,它:
-
创建了一个名为
Colour
的新类型 -
定义了两种可能的枚举值:
RED
和BLACK
-
默认情况下,
RED
对应整数值0,BLACK
对应1
②:为什么使用枚举而不是其他方式?
-
可读性:
RED
和BLACK
比数字0和1更直观 -
类型安全:
Colour
类型只能取RED
或BLACK
,不能赋其他值 -
维护性:如果需要修改颜色表示方式,只需修改枚举定义
b:插入函数(重难点)
既然是红黑树,那么我们插入的节点应该为红色还是黑色?
回顾一下性质:
1:根为黑色
2:没有连续的红色
3:每条路径都有相同个数的黑色节点
选择黑色:会违反性质3,则需要让其他路径的黑色节点也增加一个
选择红色:可能会性质2,违反需要处理
为什么说红色有可能违反2呢?因为若新增节点的父节点为黑,则没违反
正所谓:"当所有选择都暗藏代价,择其轻而承之"
所以这里肯定选择新增节点为红色,因为红色不仅有可能不违反性质,即使性质也比黑色违反性质所付出的代价更低!
这也是为什么我们a中的框架已经 _col(RED)了
颜色选定后,则插入就有以下几步了:
①:如何找到插入的位置并插入
②:检测新节点插入后,是否还是红黑树
③:处理让其恢复红黑树
①:如何找到插入的位置并插入
非常简单,和AVL树类似
代码如下:
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//不允许存在两个k值一样的节点{return false;}}//走到这里 代表找到了插入的位置cur = new Node(kv); //新的节点已经被设置为红色的if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//插入完成 则返回真return true;}
②:检测新节点插入后,是否还是红黑树
Q:那插入红色节点,什么时候代表红黑树不再是红黑树了?
//父亲存在且父亲为红代表要继续循环处理while (parent && parent->_col == RED){//处理..//让其恢复为红黑树 }/最后的根一定为黑 不再在代码中进行特判 直接暴力解决_root->_col = BLACK;return true;
Q:不是新增节点的父节点为红才代表不是红黑树吗,为什么要判断parent是否存在呢?
A:因为父节点不存在,代表新增的就是第一个节点,此时也应该跳出while循环,到外面处理_root为黑即可
其实我们上面一直以新增节点为视角来看,这是局限的!其实真实情况是处理有可能不只是需要一次,每次单次处理完成后,都有可能还不是红黑树,我们则需要继续处理
所以
①:while判断中的parent不一定只是新增节点的父节点,有可能是往上继续处理中的某个节点的父节点
②:while外面的置根节点为黑,也不一定只是新增节点为第一个节点的情况,也有可能是处理完成后,影响到了根节点的颜色,我们要把根节点恢复黑色
③:处理让其恢复红黑树
和AVL树类似,违反性质的红黑树不止一种情况,所以对应的处理的方案也不止一种
而违反规则的时候,则代表p一定为红,g一定为黑,只有u不确定(u存在为红/u存在为黑/u不存在)
所以当插入节点后,此时不再是红黑树的时候,该红黑树此时的状态就是三种:
p一定为红,g一定为黑,u存在为红 / u存在为黑/ u不存在
所以处理就是处理这个三种状态!
Q:为什么p一定为红?
A:p为黑,那还叫违反红黑树规则吗,那还需要处理吗?
Q:为什么g一定为黑?
A:因为p为红,所以g一定为黑,不然在我们新增之前就已经不是红黑树了
Q:咱们不是还没写吗,也有可能出现新增之前就不是红黑树的情况啊?
A:hehe,我们讨论的红黑树插入算法有一个重要前提:插入前树必须满足所有红黑树性质。这是算法正确性的基础假设,就像数学证明需要先明确公理一样。"
博主依旧是采取抽象图来讲解,因为例子多到数不过来,用单一的例子反而不能代表全部情况,用抽象图更好
第一种不是红黑树的情况及处理:

单次处理后:此时的g就是新的cur,然后继续进行处理(处理可能不只是用情况1的处理,下面会讲),直到某个cur的父亲不再是红色 即可
情况一处理的代码如下:
Node* uncle = grandfather->_right;// 情况一:叔叔存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}
第二种不是红黑树的情况及处理:
情况二 cur为红,p为红,g为黑,u不存在/u存在且为黑
Q:为什么u不存在/u存在且为黑 在一个情况中?
A:因为处理方案都是旋转
解释:
1.叔叔存在且为黑:
如果u节点存在,且其为黑,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。所以现在a,b,c中一定至少有一个黑色(这样才能符合每条路径黑色个数一致的性质) 举个例子如下图:
由图可知,cur的确是由黑变红的,新增节点是在cur的子树中
u存在且为黑的情况的处理:
我们无法再像情况1一样直接做变色处理即可了;我们要先旋转再做特殊变色处理才可以 !
处理如下:
咱们得旋转函数的参数是g,才能正确旋转!
如图可知:情况2一定是从情况1 往上处理遇到的一种情况
2.叔叔不存在:
如果u节点不存在,则cur一定是新插入节点
Q:为什么cur一定是新插入节点?
A:那假设cur不是新增。cur不是新增则代表其的红色是由黑色转变的(因为之前肯定符合红黑树,所以cur和p形成两个连续的红),但是现在cur为红,代表cur的子树一定有一个黑节点的,那此时的每条路径的黑色不再一致,此时从 g
到 cur
的子树的叶子节点的路径会比 g
→u
的路径多一个黑色节点(因为 cur
的子节点有黑),违反性质了。
Q:为什么p的右孩子不存在?
A:同理啊!因为g到其右孩子为空这条路径黑色为1,若此时p的右孩子存在,那么该节点就应该是黑色,那此时g到p再到p的右孩子,就是两个黑色了,违反性质了。
u不存在的处理:
u存在且为黑一样的:
一样的:旋转函数的参数是g,才能正确旋转!
但是根据我们对AVL树的了解可知,如情况2的这种p为g的左,c又为p的左所需的是右单旋,所以就单旋而言,还有右旋!
如下所示:
// 情况二:叔叔不存在或者存在且为黑// 旋转+变色 //p是g的左if (parent == grandfather->_left){//且c是p的左 if (cur == parent->_left){// g// p u(存在且为黑或不存在)// cRotateR(grandfather);//参数为g//变色处理 p变黑 g变红parent->_col = BLACK;grandfather->_col = RED;}}//p是g的右if (parent == grandfather->_right){//且c是p的右 if (cur == parent->_right){// g// u p// cRotateL(grandfather);//参数为g//变色处理 p变黑 g变红parent->_col = BLACK;grandfather->_col = RED;}}
都讲到这里了,肯定还有左右双旋和右左双旋
左右双旋:p是g的左,但是c是p的右
右左双旋:p是g的右,但是c是p的左
且变色也有不同:需要双旋之后 再c变黑 g变红
所以情况2还有其他的树状态,此时需要双旋来解决!
所以左右双旋代码如下:
//叔叔不存在或者存在且为黑//p是g的左 if (parent == grandfather->_left){if (cur == parent->_left){//右单旋处理...}else//代表c是p的右{// g// p u((存在且为黑或不存在))// c//则需左右双旋 在变色(c变黑 g变红)RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}//旋转后代表红黑树已经合理 所以break 不再通过循环判断break;}
右左双旋代码如下:
//叔叔不存在或者存在且为黑//p是g的右 if (parent == grandfather->_right){//c是p的右if (cur == parent->_right){//左单旋处理...}else//代表c是p的左{// g// u p// c//则需右左双旋 在变色(c变黑 g变红)RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}//旋转后代表红黑树已经合理 所以break 不再通过循环判断break;}
到这里咱们得插入就算完成了,但是说了这么多,也挺杂的,梳理一下吧:
④:插入函数的整体思路:
1. 基础插入部分
-
空树处理:若树为空,直接创建黑色根节点并返回。
-
BST查找插入位置:从根节点开始比较键值,找到合适的位置插入新节点(默认红色)。
-
重复值检查:若键已存在,直接返回false。
2. 红黑树性质修复
当父节点为红色时(违反红黑树性质),进入修复循环:
情况分类
根据父节点(p
)相对于祖父节点(g
)的位置分为两大分支:
A. 父节点是祖父的左孩子
-
情况1:叔叔节点(
u
)存在且为红-
操作:将父节点和叔叔节点变黑,祖父节点变红
-
后续:将祖父g节点设为当前cur节点,继续向上检查
-
-
情况2:叔叔节点不存在或为黑
-
子情况2.1:当前节点是父节点的左孩子(直线型)
-
对祖父节点右旋
-
父节点变黑,祖父节点变红
-
-
子情况2.2:当前节点是父节点的右孩子(折线型)
-
先对父节点左旋,再对祖父节点右旋
-
当前节点变黑,祖父节点变红
-
-
B. 父节点是祖父的右孩子(对称处理)
-
情况1:同A.1,方向相反
-
情况2:
-
子情况2.1:当前节点是父节点的右孩子(直线型)
-
对祖父节点左旋
-
-
子情况2.2:当前节点是父节点的左孩子(折线型)
-
先对父节点右旋,再对祖父节点左旋
-
-
3. 终止条件
-
旋转调整后直接退出循环(因为已满足性质,此时不能再通过循环判断跳出)
-
向上检查到根节点或遇到黑父节点时终止
4. 最终处理
-
强制将根节点设为黑色(保证性质根为黑)
⑤:插入函数代码如下:
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->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//父亲存在且父亲为红代表要继续循环while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){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 不再通过循环判断break;}}else//p是g的右{Node* uncle = grandfather->_left;// 情况一:叔叔存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else//情况2{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色// g// u(...) p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//c是p的左{// g// u(...) p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}//旋转后代表红黑树已经合理 所以break 不再通过循环判断break;}}}//最后的根一定为黑 不再在代码中进行特判 直接暴力解决_root->_col = BLACK;return true;}
c:旋转函数
不再赘述,AVL博客里面讲过了~
void RotateL(Node* parent){++rotateSize;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;subR->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){++rotateSize;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;subL->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}
d:检查函数
Q:检查该树是否为红黑树,那怎么检查呢?
A:根据性质来检查即可:
性质:
2:没有连续的红色
3:每条路径都有相同个数的黑色节点
2:有连续的红色?
3:存在两条路径的黑色个数不相等?
//路径的黑色个数 计算好的标准值bool Check(Node* cur, int blackNum, int refBlackNum){ //执行这里则代表:空树或算完一条路径了if (cur == nullptr){//判断一下标准值和我们算的某条路径的值是否相等if (refBlackNum != blackNum){cout << "黑色节点的数量不相等" << endl;return false;}return true;}//判断是否有连续的红if (cur->_col == RED && cur->_parent->_col == RED){cout << cur->_kv.first << "存在连续的红色节点" << endl;return false;}//遇到黑色节点则++lackNumif (cur->_col == BLACK)++blackNum;//递归 本质是前序遍历return Check(cur->_left, blackNum, refBlackNum)&& Check(cur->_right, blackNum, refBlackNum);}bool IsBalance(){//根节点不为红if (_root && _root->_col == RED)return false;int refBlackNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)refBlackNum++;//计算一条路径的黑色个数cur = cur->_left;}//然后以这条路径的黑色个数为标准值 去看其余的每条路径是否都一样return Check(_root, 0, refBlackNum);}
解释:
-
关键点:
每次递归调用都拷贝传递blackNum
值,确保:-
向左/右分支探索时,各自独立累计黑色节点数
-
不同路径的计数互不干扰
-
B1/ \R2 B3/ \ /
B4 B5 B6
-
基准值计算:最左路径
B1→B4
→refBlackNum=2
-
递归验证:
-
路径1: B1→R2→B4 → 黑色数=2(B1,B4)
-
路径2: B1→R2→B5 → 黑色数=2(B1,B5)
-
路径3: B1→B3→B6 → 黑色数=3(B1,B3,B6)→ 此处会报错
-
其余函数,中序,高度,个数,都不说了,AVL中都说过
其中的该函数使用来得到旋转次数的函数,方便和AVL树比较:所以参数也要放进成员变量中
int GetRotateSize(){return rotateSize;}
三:红黑树代码
#pragma once
#include<vector>
//枚举
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;//直接return了}//开始找插入的位置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进行插入 cur = new Node(kv); //新的节点已经被设置为红色的if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//父亲存在且父亲为红代表要继续循环while (parent && parent->_col == RED){ //记录gNode* grandfather = parent->_parent;//p为g的左if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 情况一:叔叔存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else//情况二{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色//p为g的左且c为p的左 则右单旋if (cur == parent->_left){// g// p u(存在且为黑或不存在)// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//p为g的左且c为p的右 则左右双旋{// g// p u((存在且为黑或不存在))// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}//旋转后代表红黑树已经合理 所以break 不再通过循环判断break;}}else//p是g的右{Node* uncle = grandfather->_left;// 情况一:叔叔存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else//情况2{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色// g// u(...) p// c//p是g的右且c是p的右 左单旋if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//p是g的右且c是p的左 右左单旋{// g// u(...) p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}//旋转后代表红黑树已经合理 所以break 不再通过循环判断break;}}}//最后的根一定为黑 不再在代码中进行特判 直接暴力解决_root->_col = BLACK;return true;}//左单旋void RotateL(Node* parent){++rotateSize;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;subR->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}//右单旋void RotateR(Node* parent){++rotateSize;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;subL->_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 << endl;_InOrder(root->_right);}void InOrder(){_InOrder(_root);}//检查函数 路径的黑色个数 计算好的标准值bool Check(Node* cur, int blackNum, int refBlackNum){ //执行这里则代表:空树或算完一条路径了if (cur == nullptr){//某条路径的黑色值和标准值不相等?if (refBlackNum != blackNum){cout << "黑色节点的数量不相等" << endl;return false;}return true;}//有连续的红?if (cur->_col == RED && cur->_parent->_col == RED){cout << cur->_kv.first << "存在连续的红色节点" << endl;return false;}//遇到黑色节点则++lackNumif (cur->_col == BLACK)++blackNum;//递归 本质是前序遍历return Check(cur->_left, blackNum, refBlackNum)&& Check(cur->_right, blackNum, refBlackNum);}bool IsBalance(){//根节点为红?if (_root && _root->_col == RED)return false;int refBlackNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)refBlackNum++;//计算一条路径的黑色个数cur = cur->_left;}//然后以这条路径的黑色个数为标准值 去看其余的每条路径是否都一样return Check(_root, 0, refBlackNum);}//节点个数函数size_t Size(){return _Size(_root);}size_t _Size(Node* root){if (root == NULL)return 0;return _Size(root->_left)+ _Size(root->_right) + 1;}//查找函数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 NULL;}//高度函数int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}int Height(){return _Height(_root);}//获取旋转次数函数int GetRotateSize(){return rotateSize;}private:Node* _root = nullptr;int rotateSize = 0;
};
//----------------------------------------------------------------
//以下为测试
void TestRBTree1()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int, int> t;for (auto e : a){t.Insert(make_pair(e, e));}t.InOrder();cout << t.IsBalance() << endl;
}void TestRBTree2()//测试红黑树一千万个数据的速度
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin1 = clock();RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end1 = clock();//打印插入一千万数据的时间cout << "Insert:" << end1 - begin1 << endl;//判断是否平衡cout << t.IsBalance() << endl;//打印红黑树的高度和个数cout << "Height:" << t.Height() << endl;cout << "Size:" << t.Size() << endl;size_t begin2 = clock();//查找树中的一千万个值的时间for (auto e : v){t.Find(e);}size_t end2 = clock();//打印查找的时间cout << "Find:" << end2 - begin2 << endl;
}
四:红黑树的测试
测试是和红黑树在同一个.h中的,这也是为什么红黑树代码上面包了vector,因为测试会用!
测试Test1结果如下:最后的1代表检查函数通过了,是一个红黑树
测试Test2结果如下:
五:红黑树和AVL的比较
该代码放在.c中 且.c包含AVL和红黑树的.h,才能同时用两个头文件的内容~
//红黑树和AVL的对比测试
//突出红黑树旋转次数的减少的同时 还在速度上逼近AVL树
void TestRBTree_AVLTree()
{const int N = 10000000;//一千万数据vector<int> v;v.reserve(N);srand(time(0));//先把一千万数据放进vector中for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}RBTree<int, int> t1;//红黑树对象AVLTree<int, int> t2;//AVL树对象// 红黑树插入一千万个数据的时间size_t begin1 = clock();for (auto e : v){t1.Insert(make_pair(e, e));}size_t end1 = clock();// AVL树插入一千万个数据的时间size_t begin2 = clock();for (auto e : v){t2.Insert(make_pair(e, e));}size_t end2 = clock();//打印红黑树的旋转次数cout << "RBTree RoateSize:" << t1.GetRotateSize() << endl;//打印AVL的旋转次数cout << "AVLTree RoateSize:" << t2.GetRotateSize() << endl;//打印红黑树插入一千万个数据的时间cout << "RBTree Insert:" << end1 - begin1 << endl;//打印AVL树插入一千万个数据的时间cout << "AVLTree Insert:" << end2 - begin2 << endl;//判断红黑树是否平衡cout << "RBTree IsBalance:" << t1.IsBalance() << endl;//判断AVL树是否平衡cout << "AVLTree IsBalance:" << t2.IsBalance() << endl;//打印红黑树高度cout << "RBTree Height:" << t1.Height() << endl;//打印红黑树节点个数cout << "RBTree Size:" << t1.Size() << endl;//打印AVL树高度cout << "AVLTree Height:" << t2.Height() << endl;//打印AVL树节点个数cout << "AVLTree Size:" << t2.Size() << endl;// 红黑树查找所有值的时间size_t begin3 = clock();for (auto e : v){t1.Find(e);}size_t end3 = clock();//AVL树查找所有值的时间size_t begin4 = clock();for (auto e : v){t2.Find(e);}size_t end4 = clock();//红黑树和AVL树查找所有值分别花的时间cout << "RBTree Find:" << end3 - begin3 << endl;cout << "AVLTree Find:" << end4 - begin4 << endl;
}
测试结果如下:
由图可知:
一千万个数据下
红黑树在速度上仅仅慢了AVL树5毫秒,但是红黑树所用的旋转次数降低了接近100w次,插入也稍快,由此可见红黑树是一个优秀的结构!
六:AVL树代码
需要的直接cv
相对与AVL树博客有少许改动,是为了和红黑树比较
#pragma once
#include<assert.h>
#include<vector>//节点类
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorpair<K, V> _kv;AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr) //指向父节点的之怎, _bf(0) //平衡因子, _kv(kv) //pair类型的对象 存储k值和v值{}
};
//AVL类
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://插入函数bool Insert(const pair<K, V>& kv){//空树情况if (_root == nullptr){_root = new Node(kv);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//AVL树不会存在相同的节点{return false;}}//走到这代表 找到了插入的位置 cur = new Node(kv);//先把该节点准备好//parent节点在之前是cur的父节点 也就是空节点的父亲//所以现在能将cur正确的链接到parent的正确方向if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//插入完成一定会影响parent节点的bf(只是看cur是p的哪一边 bf再++或--)//p的bf三种情况:bf 0 1/-1 2/-2//0:不会影响parent的祖先的bf 直接break//1/-1:树高度增加 会影响祖先的bf 所以更新完p的bf 再次循环继续更新上面的bf//2/-2:则需要旋转while (parent){//第一次更新p的bfif (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//对更新完的p的bf判断if (parent->_bf == 0){break;}//祖先节点依旧需要更新bf 再次循环else if (parent->_bf == 1 || parent->_bf == -1){cur = cur->_parent;parent = parent->_parent;}//需要旋转//进入旋转,咋代表cur和parent 之前已经循环找了 需要旋转的p节点//(该p节点的bf 2/-2)else if (parent->_bf == 2 || parent->_bf == -2){// 左旋转// 触发条件:p->bf为2 cur->_bf == 1if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}// 右旋转// 触发条件:parent->_bf == -2 && cur->_bf == -1else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}// 左右旋转// 触发条件:parent->_bf == -2 && cur->_bf == 1else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}//除此之外右左旋转else{RotateRL(parent);}break;}else{// 插入之前AVL树就有问题assert(false);//标记“不应执行到此处”}}//插入完成 则返回真return true;}//左旋转//新节点插入较高右子树的右侧---简称右右//代表从次树的_root节点看右子树高1 且插入的节点在右子树的右侧//步骤: //①:给到p的右指针指向原先p的右孩子的左子树//②:p的右子树的的左指针指向p节点void RotateL(Node* parent)//参数的p节点 就是bf为2的节点{++rotateSize;Node* subR = parent->_right; //p的右Node* subRL = subR->_left; //p的右左parent->_right = subRL;//①if (subRL) //避免p的右左为空subRL->_parent = parent;subR->_left = parent;Node* ppnode = parent->_parent; //先记录p的父节点 以防p节点不是根节点parent->_parent = subR; //②if (parent == _root)//查看p是否为根节点{//p是根节点 则subR成为新的根接节点//再置一下subR父指针为空_root = subR;subR->_parent = nullptr;}else//p不是根 则p的父亲也需要正确的指向subR(判断原先的p是pp的左右孩子的哪一个){if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = 0;subR->_bf = 0;}//右旋转//新节点插入较高左子树的左侧 简称左左//代表从次树的_root节点看左子树高1 且插入的节点在左子树的左侧//步骤: //①:p的左指针指向p原先的左孩子的右孩子//②:原先的p的左孩子的右指针指向p节点//代码逻辑和左旋转同理 不再赘述void RotateR(Node* parent){++rotateSize;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;subL->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}subL->_bf = 0;parent->_bf = 0;}//左右旋转->先左单旋再右单旋//新节点插入较高左子树的右侧//步骤 ://①:对p的左节点进行左单旋//②:再对p节点进行右单旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//由于在b树插入 在c树插入 或60本身就是插入的节点入 这三种情况双旋之后的树的bf不同//规律:根据插入节点的bf判断即可if (bf == -1)//代表在b树插入{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1)//代表在c树插入{subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}//else if (bf == 0)//代表subLR就是插入节点{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//右左旋转//新节点插入较高右子树的左侧---右左:先右单旋再左单旋//步骤 ://①:对p的右节点进行右单旋//②:再对p节点进行左单旋//代码逻辑类似 不再赘述void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << "[" << root->_bf << "]" << endl;_InOrder(root->_right);}//中序遍历void InOrder(){_InOrder(_root);}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}//高度函数int Height(){return _Height(_root);}//bool _IsBalance(Node* root) {// if (root == nullptr) {// return true;// }// int leftHeight = Height(root->_left);// int rightHeight = Height(root->_right);// // 检查当前节点是否平衡(左右子树高度差不超过1)// if (abs(rightHeight - leftHeight) >= 2) {// cout << root->_kv.first << " 不平衡" << endl;// return false;// }// // 检查平衡因子是否正确(bf应等于右子树高度减左子树高度)// if (rightHeight - leftHeight != root->_bf) {// cout << root->_kv.first << " 平衡因子异常" << endl;// return false;// }// // 递归检查左右子树// return _IsBalance(root->_left) && _IsBalance(root->_right);//}//更优秀的平衡函数bool _IsBalance(Node* root, int& height){if (root == nullptr){height = 0;return true;}int leftHeight = 0, rightHeight = 0;if (!_IsBalance(root->_left, leftHeight)|| !_IsBalance(root->_right, rightHeight)){return false;}if (abs(rightHeight - leftHeight) >= 2){cout << root->_kv.first << "不平衡" << endl;return false;}if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;return true;}//是否平衡判断函数bool IsBalance(){int height = 0;return _IsBalance(_root, height);}//计算树的节点个数size_t Size(){return _Size(_root);}size_t _Size(Node* root){if (root == NULL)return 0;return _Size(root->_left)+ _Size(root->_right) + 1;}//查找函数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 NULL;}int GetRotateSize(){return rotateSize;}
private://成员变量_root根节点Node* _root = nullptr;int rotateSize = 0;
};