目录
二叉搜索树的概念
AVL树的概念
一颗AVL树或是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1
注意:平衡因子是右子树的高度减左子树的高度
AVL树节点的实现
#pragma once
#include<iostream>
using namespace std;
//AVL树的节点
template<class K,class V>
struct AVLTreeNode
{
pair<K, V> _kv; //KV键值对
AVLTreeNode<K, V>* _left, *_right, *_parent; //分别指向左子树、右子树、父节点
int _bf; //平衡因子,用来判断平衡
//构造函数
AVLTreeNode(const pair<K,V>& kv)
:_kv(kv)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
};
二叉搜索树插入操作
在实现AVL树节点插入之前我们先实现一个简单的二叉搜索树的插入操作:
为了找到要插入的位置,定义两个节点指针,parent和cur,开始遍历cur,要插入的值比cur小cur就往左移动,要插入的值比cur大,cur就往右移动,parent一直保持为cur的父节点指针,直到cur为空后,判断parent和插入节点的值,如果插入节点比parent小,则插入在parent左边,反之在右边
template<class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
AVLTree()
:_root(nullptr)
{}
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 (kv.first < cur->_kv.first)//插入节点在cur的左边
{
parent = cur;
cur = parent->_left;
}
else if (kv.first > cur->_kv.first)//插入节点在cur的右边
{
parent = cur;
cur = parent->_right;
}
else
return false;
}
//到这里cur已经为空了,kv找到了他要插入的位置
cur=new Node(kv);
if (kv.first < parent->_kv.first)//cur为parent的左孩子
{
parent->_left = cur;
cur->_parent = parent;
}
else //cur为parent的右孩子
{
parent->_right = cur;
cur->_parent = parent;
}
return true
}
平衡因子的更新
现在我们已经对节点进行了插入,接下来我们要对平衡因子进行更新,首先根据插入的cur为parent的右节点还是左节点进行判断,分别对parent->_bf进行++和--操作之后对parent进行持续更新,parent是否继续更新依据:子树高度是否变化、是否更新完了根节点
1.parent->_bf == 0,说明之前parent->_bf是1 或者 -1,说明之前的parent一边高一边低,这次插入填上了矮的那一边,parent所在子树高度不变,不需要继续更新
2.parent->_bf == 1 或 -1,说明之前parent->_bf是0,parent所在子树高度变高了,需要parent作为cur,parent的父节点作为新的parent进行更新
3.parent->_bf == 2 或 -2,说明已经严重不平衡,需要就地旋转处理(具体旋转方法下面讲解)
//判断是否失衡
while (parent)
{
int& bf = parent->_bf;
if (parent->_right == cur)
bf++;
else if (parent->_left == cur)
bf--;
if (bf == 0)break;
else if (abs(bf) == 2)
{
//旋转操作
//后面进行添加
}
else if (abs(bf) == 1)
{
cur = parent;
parent = cur->_parent;
}
else
{
cout << "调节错误(出现大于2的bf)";
exit(1);
}
}
旋转操作
旋转根据AVL树的性质分别由三个要求:保持他为二叉搜索树、更新平衡因子、让这棵树左右高度不超过1、降低子树高度(不需向上更新)
左单旋
![](https://i-blog.csdnimg.cn/direct/792004f31ca24474828dae105aabe38b.png)
abc表示高度为h的AVL树,新节点插入c下方导致cur->_bf==1 && parent ->_bf ==2,此时需要进行左单旋,parent(30)的右节点指针指向b,cur(60)左节点变成parent(30),cur(变为该子树新的根),cur和parent的平衡因子都变为0(从图中可简单看出)
void RotateL(Node* parent)
{
Node* subR = parent->_right; //parent的右节点也就是cur
Node* subRL = subR->_left; //parent的右节点的左节点,也就是c
//将subRL也就是c移到parent的右边
parent->_right = subRL;
if (subRL)//要保证c不为空
subRL->_parent = parent;
Node* ppNode = parent->_parent; //保存parent的parent
//将parent,移动到subR也就是cur的左边
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)//parent为根节点的情况
{
_root = subR;
_root->_parent = nullptr;
}
else
{ //让subR也就是cur称为新的子树的根
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
//修改平衡因子
parent->_bf = subR->_bf = 0;
}
右单旋
插入新节点后,cur->_bf==-1 && parent ->_bf ==-2,则需要进行右单旋,原理与左单旋相似不进行赘述
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 (_root == parent)
if (ppNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
左右双旋
ad为高度为h的AVL树,bc为高度为h-1的AVL树,向b树插入新的节点以后,导致60处节点变为了-1,30处节点(cur)变为了1,90处节点(parent)变味了-2。也就是parent->_bf == -2 && cur->_bf == 1时,进行左右单旋。操作方法是,先以30为parent进行左单旋,再以90为parent进行右单旋,最后更新平衡因子,新增节点在60的左边、右边、或者60就是新增因子这三种情况平衡因子有所不同,需要分情况讨论。
void RotateLR(Node* parent)
{
Node* subL = parent->_left; //30
Node* subLR = subL->_right; //60
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
//更新平衡因子
if (bf == -1) // subLR左子树新增
{
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else if (bf == 1) // subLR右子树新增
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0) // subLR自己就是新增
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
右左双旋
插入新节点后,cur->_bf==-1 && parent ->_bf ==2,则需要进行右左单旋,原理与左右单旋相似不进行赘述
插入操作中补充旋转
把所有旋转操作以及旋转条件都进行实现和指明了之后,我们就可以补充更新平衡因子时,出现不平衡状态需要旋转部分的代码
// 旋转
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;