文章目录
- 前言
- 1.AVL树的相关介绍
- 2.AVL树的旋转
- 1.失衡状态
- 2.旋转调整
- 3.代码实AVL树
- 1.节点的插入
- 2.插入部分的验证
- 4.红黑树的相关介绍
- 5.红黑树的插入调整
- 1.处理方式
- 2.代码实现
- 6.红黑树的检查
- 7.总结
前言
之前介绍了二叉搜索树,本文主要是对AVL树和红黑树进行介绍。普通的二叉搜索树插入结点之后可能会失去平衡,退化成单链表形式造成查找效率低下,因此引入了二叉搜索树和红黑树,在插入节点后会进行一定的处理维持树的平衡性,提高查找效率。本文主要介绍的是插入节点后处理,对删除节点没有做介绍。
1.AVL树的相关介绍
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年
发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树的特性
对任何一个节点来说它的左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)的二叉搜索树就是AVL树,空树也可以看出AVL树。
当在AVL树中插入节点后,可能会因为造成失衡,也就是平衡因子可能会超过2.这个时候就需要旋转处理,因此来维持AVL树的特性,AVL树一旦失衡也就不再是AVL树了。AVL树最精华核心的地方就是旋转。
2.AVL树的旋转
1.失衡状态
AVL树的旋转是插入节点造成AVL树的平衡因子大于2之后一种维持树的平衡性的操作,
造成失衡的情况有4种情况,分别是LL型,RR型,LR型,RL型这4种状态失衡。
具体情况我们看图分析。
对于一颗平衡的AVL树来说插入节点会影响到从当前节点到根节点路径上的平衡因子,也就是说它会造成某一条路径上的节点的子树失衡甚至影响到根节点的子树的高度差失衡。
这个失衡状态是从下往上影响的,因为AVL树要求左右子树高度差不超过1,可能原来某个节点的子树高度差是1插入节点成了0,更加平衡了,这个时候一定能保证这个该节点开始往后的所有路径上节点的子树都是平衡的,就不用管了。但是也可能某个节点子树高度差不满足这个条件了。针对失衡状态引入上述模型图,当插入节点后,插入路径上的某个节点的子树的失衡了,不外乎上述4种情况。该子树路径中左孩子的左子树失衡,这是LL型,右孩子的右子树失衡这是RR型,左孩子的右子树失衡这是LR型,右孩子的左子树失衡这是RL型。
2.旋转调整
在AVL树中出现失衡状态就会旋转处理,根据失衡状态的不同旋转处理的方式。
LL是右单旋,RR是左单旋,LR是左右双旋,RL是右左双旋。
具体的旋过程我们来看图。
关于旋转这块只能画图理解,
至于为什么非得这么旋转,这里由于我才疏学浅给不出证明,大家可自行研究,但是可以保证的是当插入路径中某个节点的子树不平衡后,通过上述的旋转一定能保证AVL树的平衡性。
上述理论部分完了之后,接下来就是写代码了。我们来看看代码应该怎么写。
3.代码实AVL树
AVL树本质也是二叉树搜索树,这里选用KV模型的二叉搜索树来实现AVL树。
节点的定义
template <class K ,class V>
struct AVLTreeNode
{
AVLTreeNode <K, V>*_left;
AVLTreeNode <K, V>*_right;
AVLTreeNode <K, V>* _parent;
pair<K, V> _kv;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{
;
}
};
AVL树每个节点中需要一个字段bf记录当前节点左右子树的高度差。
这里我将bf定义为右子树高度减去左子树的高度。
当然这只是我的定义方式,大家也可以将bf定义为左子树的高度减去右子树高度。因为每次插入节点该插入节点路径的上的节点左右子树的bf都会被更新。这里定义parent指针指向每个节点的父节点。这样方便自底向上调整bf和旋转处理。根节点的parent是空,因为根节点没有父节点。
1.节点的插入
节点插入开始的时候就很普通二叉搜索树一样,比较大小插入进树中最后连接上。但是连接上后需要更新平衡因子,当平衡因子大于1或者小于-1的时候就需要调整了。
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
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
//更新平衡因子
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
// 继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
{
break;
}
//旋转处理
else if (parent->_bf == 2 || parent->_bf == -2)
{
//LL型右旋
if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
//RR型左旋
else if(parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
//LR型 左右双旋
else if (parent->_bf == -2&&cur->_bf==1)
{
RotateLR(parent);
}
//RL型 右左双旋
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
cout << "Insert assert" << endl;
assert(false);
}
}
return true;
}
因为有个_parents指针,当cur节点和对应的父节点连接上后,cur的_parent指向也要指向对应的父节点进行更新。之后就是更新因子,因为我们定义bf是右子树的高度减去左子树的高度,如果cur是parent的右节点,parent的bf++,反之bf- -,因为该插入路径上节点的bf会被影响,因此需要自底向上不断更新bf。如果更新后某个节点的bf是为0的,就不用继续向上更新即可,直接跳出循环。如果bf2或者bf-2的时候就需要旋转处理了。
如果bf大于2或者bf小于-3那说明之前的更新就有问题了。在代码中写个断言,这样确保我们程序出错后快速定位问题,同时也可提早发现程序问题。这是一个不错的coding技巧。
假如某次更新后parens出现bf为2或者-2,该如何确定应该用那种方式处理呢?
更新后parent的bf为2或者为-2时,进行调整的时候需要根据先前的图来确定处理方式。
LL型是parents的左子树中出现问题,且是左孩子的左子树的出现问题。
对应的就是parents的bf==-2&&cur的bf==-1,为什么?我们定义的是bf==右子树减去左子树。当parent的左树出现问题时,肯定是左树高对应也就是bf==2,cur又是parent的左孩子,那么cur的左树也是较高的那一方,对应的就是cur==-1.
同理我们也可以推倒出其他的类型情况,当parent的bf==-2 && cur的bf == 1 时肯定就是LR型,当parent的bf == 2 且cur 的bf==-1时肯定就是RL型,当parent的bf2,且cur1肯定就是RR型。这些都是结合图以及左右子树的高度差分析出来的,并不是凭空想象的。先前的图很重要,需要细细品味。
知道了在哪种情况下应该用哪种处理方式,接下来就是实现旋转了。
右单旋
void RotateR(Node* parent)
{
Node* child = parent->_left;
Node* ppnode = parent->_parent;
parent->_left = child->_right;
if (child->_right)
{
child->_right->_parent = parent;
}
child->_right = parent;
parent->_parent = child;
if (parent == _root)
{
_root = child;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = child;
}
else
{
ppnode->_right =child;
}
child->_parent = ppnode;
}
child->_bf = parent->_bf = 0;
}
这里右旋的操作还是先看看之前的图,我们这里右单旋的parent的左孩子的左子树出现了问题,这里先把左孩子用变量保存起来,从之前的图中的我们可以看到左孩子节点的右孩子部分是连接在上parent的左指针上的。这个时候需要改变child的右孩子的_parent的指向,
但是这个child的右孩子可能会是空,这个时候需要先判断一下child的右孩子是否为空在考虑更新_parent的指向。
同样的parent的_parent的指向也需要更新,更新为child节点。同时这个parent的也将成为chil的右孩子,这个时候child的_parent的指向也需要更新,在将这个child和之前的parent的双亲节点进行链接。如果之前parent的双亲节点是_root需要单独处理一下。以上链接部分可能文字叙述比较绕我建议还是对着图来链接。
这里简单理解总结一下链接步骤。
child的右孩子成为parent的左孩子,parent成为chid的右孩子。
这个时候需要更新3个节点的双亲节点的指针,child ,child的右孩子,parent的双亲节点都应该更新。这个child的右孩子可能为空,这个需要单独处理。之后需要将之前parent的双亲节点和child建立连接关系。
这样算是连接成功,之后最后一步,就是更新平衡因子,这里只有child的平衡因子和parent的平衡因子的需要更新,且更新为0.为什么呢?
再来看看图。
总的来说右旋之后parent和child的bf一定为0。接下来我们来看看左旋操作。
左单旋
//左单旋操作
void RotateL(Node* parent)
{
Node* ppnode = parent->_parent;
Node* child = parent->_right;
parent->_right = child->_left;
if (child->_left)
{
child->_left->_parent = parent;
}
child->_left = parent;
parent->_parent = child;
if (ppnode == nullptr)
{
_root = child;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = child;
}
else
{
ppnode->_right = child;
}
child->_parent = ppnode;
}
parent->_bf = child->_bf = 0;
}
这里左旋的时候child是parent的右孩子,先把parent的双亲节点保存起来,之后child和建立新的链接关系的时候需要用到。之后和右旋一样,链接新的指向关系,更新双亲节点的指向。parent和child的平衡因子都得更新成为0.这些指向关系以及平衡因子的更新都得看先前的那张图。
其实这里文字叙述越多反而越乱。
接下来看看双旋,左右双旋就是一次左旋,一次右旋,右左双旋就是一次右旋,一次左旋。
我们直接复用代码即可,主要需要搞清楚平衡因子的更新。
//左右双旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
subLR->_bf = 0;
subL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = 0;
}
else
{
assert(false);
}
}
这里左右双旋,先左旋parent的左孩子,在右旋parent的右孩子。这点对着图来看,我们谈及一下这个平衡因子的更新。
这个bf的更新就是取决于child的右孩子的bf,这里我们最好还是对着图来分析写代码。
因为旋转之后,节点会移动,所以在旋转之前保存一下对应的节点,这样才能正确的更新节点的bf。
这样我们先来看看这个右左双旋。
右旋双旋
void RotateRL(Node* parent)
{
Node* child = parent->_right;
Node* subRL = child->_left;
int bf = child->_left->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
child->_bf = 0;
parent->_bf = -1;
subRL->_bf=0;
}
else if (bf == -1)
{
child->_bf = 1;
parent->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)
{
child->_bf = 0;
parent->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
右左双旋,就是先右旋parent的右孩子,在左旋parent.接着还是平衡因子的更新,平衡因子的更新和左右双旋类似,我们画图分析一下。
其实上述图都是模型图,就相当于将插入节点后,插入路径中的某个失衡节点以及其左右子树单独拿出来分析。这样便于分析,以上的代码细节都可以从图中找到答案。
这里最后总结一下:左旋RR失衡,
需要parent和child以及chid的左孩子,这里的child是parent的右孩子。
右旋LL失衡,需要parent和child以及child的右孩子,这里child是parent的左孩子。
左右双旋是LR型,需要左旋parent的左孩子,再右旋parent。
右左双旋RL型,右旋parent的右孩子,再左旋parent。
2.插入部分的验证
当我们实现好了插入部分,需要验证一下结果是否正确。这里需要写两个函数,一个判断树是否是平衡的,还有一个就是求树高。因为判断树是否平衡需要用到树高。
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;
}
int Height()
{
return _Height(_root);
}
bool _IsBalance(Node* root)
{
if (root == NULL)
{
return true;
}
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
if (rightH - leftH != root->_bf)
{
cout << root->_kv.first << "节点平衡因子异常" << endl;
return false;
}
return abs(leftH - rightH) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
bool IsBalance()
{
return _IsBalance(_root);
}
在判断树是否平衡这里,多加了一个判断条件,我们的平衡因子更新是否出错。如果平衡因子更新出错,这是属于隐藏的bug,不容易发现。这样可以提前排查问题,确定程序的正确性。
完整代码
#include<assert.h>
#include<map>
#include<iostream>
using namespace std;
template <class K ,class V>
struct AVLTreeNode
{
AVLTreeNode <K, V>*_left;
AVLTreeNode <K, V>*_right;
AVLTreeNode <K, V>* _parent;
pair<K, V> _kv;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{
;
}
};
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
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
//更新平衡因子
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
// 继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
{
break;
}
//旋转处理
else if (parent->_bf == 2 || parent->_bf == -2)
{
//LL型右旋
if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
//RR型左旋
else if(parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
//LR型 左右双旋
else if (parent->_bf == -2&&cur->_bf==1)
{
RotateLR(parent);
}
//RL型 右左双旋
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
cout << "Insert assert" << endl;
assert(false);
}
}
return true;
}
//左单旋操作
void RotateL(Node* parent)
{
Node* ppnode = parent->_parent;
Node* child = parent->_right;
parent->_right = child->_left;
if (child->_left)
{
child->_left->_parent = parent;
}
child->_left = parent;
parent->_parent = child;
if (ppnode == nullptr)
{
_root = child;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = child;
}
else
{
ppnode->_right = child;
}
child->_parent = ppnode;
}
parent->_bf = child->_bf = 0;
}
//右单旋操作
void RotateR(Node* parent)
{
Node* child = parent->_left;
Node* ppnode = parent->_parent;
parent->_left = child->_right;
if (child->_right)
{
child->_right->_parent = parent;
}
child->_right = parent;
parent->_parent = child;
if (parent == _root)
{
_root = child;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = child;
}
else
{
ppnode->_right =child;
}
child->_parent = ppnode;
}
child->_bf = parent->_bf = 0;
}
//左右双旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
subLR->_bf = 0;
subL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* child = parent->_right;
Node* subRL = child->_left;
int bf = child->_left->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
child->_bf = 0;
parent->_bf = -1;
subRL->_bf=0;
}
else if (bf == -1)
{
child->_bf = 1;
parent->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)
{
child->_bf = 0;
parent->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
void Inorder()
{
_Inorder(_root);
}
void _Inorder(Node*root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_kv.first <<" ";
_Inorder(root->_right);
}
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 _IsBalance(Node* root)
{
if (root == NULL)
{
return true;
}
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
if (rightH - leftH != root->_bf)
{
cout << root->_kv.first << "节点平衡因子异常" << endl;
return false;
}
return abs(leftH - rightH) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
bool IsBalance()
{
return _IsBalance(_root);
}
int Height()
{
return _Height(_root);
}
private:
Node* _root=nullptr;
};
测试代码
#include"AVLTree.h"
void Test_AVLTree1()
{
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<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_AVLTree2()
{
srand(time(0));
const size_t N = 9999999;
AVLTree<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;
cout << t.Height() << endl;
}
int main()
{
Test_AVLTree1();
Test_AVLTree2();
}
4.红黑树的相关介绍
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。之所以会有红黑树,是因为AVL树插入节点后需要频繁的旋转调整,这样会影响效率,由此才有了红黑树这种这种折中的方案,红黑树在保证查找效率的同时,减少了节点旋转调整从次数,从而提高效率。
红黑树的相关特性
1.个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
其实红黑树的几条特性主要就保证红黑树中这样的的特性:
红黑树中不能出现出现连续红色节点,红黑树的中每个节点到其所以后袋的简单路径上黑色节点的数目是相同的。
通过这样的条件限制后,红黑树中最长的路径是全是黑色,最短的路径一半黑一半红。假设最短路径节点有n个节点,那么最长路径就是2*n个节点。那么单次查找效率在logN到log2N之间基本上还是logN的时间复杂度,同时也没有那么多旋转处理了,红黑树整体效率大大提高。
如图就是一颗红黑树,
红黑树中的叶子节点一般是指空节点,而不是像之前二叉树中度为0的节点。
5.红黑树的插入调整
1.处理方式
和AVL树一样,红黑树也是需要调整的,当插入节点是遇见连续的红色就需要调整了。也就说当插入节点后,双亲节点和孩子节点都是红色就需要调整了。对比AVL树红黑树的调整情况可能多了一点,但是调整比较简单。大致分为改变颜色处理,和旋转处理这两种处理方式。同时有分为3个大情况,其实细分下来是6种情况的。
我们来看看图吧。
上图包含了红黑树的所有处理情况,需要考虑grandfather,parent,uncle,cur,这4这个节点的关系。其实红黑树的处理是对称的,这里如果用文字叙述还是太繁琐了还是建议看图。我这里简单总结一下红黑树的处理方式吧。
如果存在叔叔节点,且叔叔节点是红色就需要用到变色处理。
且变色处理方式统一为parent和uncle节点为黑色,grandfather为红色。
如果叔叔节点不存在或者叔叔节点黑色就需要旋转加变色处理。这里分为两大类,每类中又分为两小类。
大类是根据grandfather和parent的关系划分的,小类是根据cur和parent的关系划分的。
第一大类:grandfather的左孩子是parent。
情况1,cur是parent的左孩子。右旋grandfather,parent变黑,grandfather变红色。情况2,cur是parent的右孩子,先左旋parent再右旋grandfather,cur变黑色,grandfather变红色。
第二大类:grandfather的右边孩子是parent。
情况1,cur是parent的左孩子。先右旋parent再左旋grandfather,cur变黑色,grandfather变红色。情况2,cur是parent的右孩子。左旋grandfather,parent变黑,grandfather变红色。
2.代码实现
之前介绍处理方式,接下来就是写代码了
代码示例
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)
{ // 情况1:u存在且为红,变色处理,并继续往上处理
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else // (grandfather->_right == parent)
{
Node* 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
{
if (cur == parent->_right)
{
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
节点的定义
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)
{}
};
这里我们写代码的时候,new出的节点默认应该是红色的。为什么呢?
因为如果整棵树节点全是黑色的也是满足红黑树的性质,这样一来谈何变化呢?没变化有又怎么去做节点的调整处理呢?同样这也违背红黑树设计出来的初衷:构造一颗近似平衡的二叉搜索树。
只有默认节点是红色这样一来才会有调整的可能。
这里每次插入后如果双亲节点和孩子节点都是红色就需要调整了。这里每次调整之后,
最后一定要把root根节点设置成黑色
。因为调整之后root节点可能不为黑了,这样才能保证红黑树的特性。关于代码细节实现还是需要对比上述图,且需要多画图自己体会一下。这些操作其实都是固定死的,但是想自己完整独立写出来还是需要多练习和画图思考的。
6.红黑树的检查
这个红黑树也是需要检查的,这里检查要针对红黑树的特性进行检查。
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);
}
bool _Check(Node* root, int blackNum, int benchmark)
{
if (root == nullptr)
{
if (benchmark != blackNum)
{
cout << "某条路径黑色节点的数量不相等" << endl;
return false;
}
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);
}
先检查红黑树的根节点是否为黑色,之后在求一条路径上的黑色节点数量,之后用于对比路径上的黑色节点个数。这里我选择的是一路向左,求得路径后,在写一个检查函数,用来对比所以个节点的路径上黑色节点的个数。如果出现了连续的红色节点也是错误的。这样检查红黑树就考虑的比较全面了。
完整代码
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:
~RBTree()
{
_Destroy(_root);
_root = nullptr;
}
public:
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;
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else // (grandfather->_right == parent)
{
Node* 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
{
if (cur == parent->_right)
{
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(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;
}
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 _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
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;
}
}
private:
Node* _root = nullptr;
};
测试代码
#include"RBTree.h"
void Test_RBTree1()
{
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };
RBTree<int, int> t1;
for (auto e : a)
{
t1.Insert(make_pair(e, e));
}
t1.InOrder();
cout << endl;
cout << t1.IsBalance() << endl;
}
void Test_RBTree2()
{
srand(time(0));
const size_t N = 9999999;
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;
cout << t.Height() << endl;
}
int main()
{
Test_RBTree1();
Test_RBTree2();
return 0;
}
7.总结
以上就红黑树和AVL树插入节点的操作了,红黑树和AVL树怎么说呢,它的相关操作都是固定死的,但是想正确流畅的写出来还是需要自己多练习和画图思考的,尤其是的画图,才能更为深刻理解旋转过程。以上内容如有问题,欢迎指正!