目录
AVL树概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
❍ 它的左右子树都是AVL树
❍ 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
$O(log_2 n)$,搜索时间复杂度O($log_2 n$)。
AVL树节点的定义
template<class K, class V>
struct AVLTreeNode{
pair<K,V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;//balance factor
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv),
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_bf(0)
{
}
};
AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:
◉ 按照二叉搜索树的方式插入新节点
◉ 整节点的平衡因子
插入节点,会影响部分祖先节点的平衡因子
更新平衡因子
◉ 插入在左子树,平衡因子--
◉ 插入在右子树,平衡因子++
是否继续往上跟新祖先,要看parent所在子树的高度是否变化
1.parent的平衡因子 == 0
说明parent的平衡因子更新前是1 or-1,插入节点插入矮的那边 parent所在子树的高度不变,不需要继续往上更新
2.parent的平衡因子 == 1/-1
说明parent的平衡因子更新前是0,插入节点插入在任意一边 parent所在的子树高度都会变化了,需要继续往上更新
3.parent的平衡因子 == 2/-2
说明parent的平衡因子更新前是1/-1,插入节点插入在高的那边 进一步加剧了parent所在的子树的不平衡,已经违反违规了,需要旋转处理
AVL树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
新节点插入较高左子树的右侧---右右:左单旋
旋转原则:
◉ 保持搜索树的规则
◉ 控制平衡,降低高度(右边高,往左边旋)
由上图我们可以观察到以下几点:
✸ 我们要旋转的是平衡因子失调的节点,在图中也就是parent
节点
✸ 由于二叉树的性质可知 subRL
的值b,大小位于parent
与 subR
之间,因此可以将左边高度升高,右边高度降低,也就是将subRL
连接在parent
的右边。之后使subR
作为新的“根”节点,parent
作为新根节点(subR
)的左边。
✸ 平衡因子失调的节点可能是根节点,也有可能不是根节点,因此我们需要判断+保存失调节点的父节点
✸ 子树h的高度可能为0,因此subRL
有可能为空,此时需要注意空节点的问题
✸ 改变新节点左右指针的指向时,也需要及时更改节点的父母值
✸ 平衡因子只更新parent
与subR
为0,因为其他的我们没有动
✸ 插入位置是直线,更新位置是个弯
//右边高-->左单旋
void RotateL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* pparent = parent->_parent;//检验parent是根还是子树
parent->_right = subRL;
if(subRL)//高度不为0的时候在指向,否则出现空指针指向问题
subRL->_parent = parent;
subR->_left = parent;
parent->_parent = subR;
if(pparent == nullptr){
//说明parent原先是根
_root = subR;
subR->_parent = nullptr;
}else{
//说明parent原先是子树
if(pparent->_left == parent)
pparent->_left = subR;
if(pparent->_right == pparent)
pparent->_right = subR;
subR->_parent = pparent;
}
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
新节点插入较高左子树的左侧---左左:右单旋
//右旋
void RotateR(Node* parent){
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* pparent = parent->_parent;
parent->_left = subLR;
if(subLR)
subLR->_parent = parent;
subL->_right = parent;
parent->_parent = subL;
if(pparent == nullptr){
_root = subL;
subL->_parent = nullptr;
}else{
if(pparent->_left == parent)
pparent->_left = subL;
if(pparent->_right == parent)
pparent->_right = subL;
subL->_parent = pparent;
}
subL->_bf = parent->_bf = 0;
}
新节点插入较高右子树的左侧---右左:先右单旋再左单旋
由于新节点插入在右子树的左侧:
◉ 对于节点90来说,左子树升高,需要右旋
◉ 对于节点30来说,右子树升高,需要左旋
插入后平衡因子需要改变,此时分为两种情况,h = 0 与 h > 0,其中 h>0 又包括插入左子树还是右子树
由上图我们可以观察到以下几点:
✸ 无论是怎么旋转,我们的目标都是:降高度,拉平衡,关键在于找到subRL
✸ 无论插入在subRL的左子树还是右子树,都不扰乱旋转的次序
✸ 插入的时候要维护新插节点在右子树左端的左节点还是右节点,更新平衡因子
✸ 如果我们不看过程,只看结果会发现就是使subRL作为新根节点,subRL的左右孩子分别送给左子树的右孩子,右子树的左孩子。
void RotateRL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;//维护新插节点在右子树左端的左节点还是右节点
//先右旋
RotateR(parent->_right);
//在左旋
RotateL(parent);
if(bf == 0){
// subRL就是新插节点,无孩子
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}else if(bf == -1){
// 新节点插入在subRL的左端
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}else if (bf == 1){
// 新节点插入在subRL的右端
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else{
assert(false);
}
}
新节点插入较高左子树的右侧--左右:先左单旋再右单旋![](https://i-blog.csdnimg.cn/direct/32356ed96d894e7a98dff7e6be2d2e91.png)
void RotateLR(Node* parent){
Node* subL = parent->_left;
Node* subLR = subL->_right;
int _bf = subLR->_bf;
RotateR(parent->_left);
RotateL(parent);
if(_bf == 0){
subL->_bf = 0;
subLR->_bf = 0;
parent = 0;
}else if (_bf == -1){
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}else if (_bf ==1){
subL->_bf = -1;
subLR->_bf = 0;
parent = 0;
}else{
assert(false);
}
}
AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
◉ 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
◉ 验证其为平衡树
1.每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
2.节点的平衡因子是否计算正确
void TestAVLTree1()
{
//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13};
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<int, int> t1;
for (auto e : a)
{
/*if (e == 4)
{
int i = 0;
}*/
// 1、先看是插入谁导致出现的问题
// 2、打条件断点,画出插入前的树
// 3、单步跟踪,对比图一一分析细节原因
t1.Insert({e,e});
if(e == 6){
int a = 1;
}
cout <<"Insert:"<< e<<"->"<< t1.IsBalance() << endl;
}
t1.InOrder();
cout << t1.IsBalance() << endl;
}
AVL树的删除(了解)
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
具体实现学生们可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即$log_2 (N)$。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
项目文件
//
// AVLTree.h
// AVL树
//
// Created by 南毅 on 2024/7/19.
//
#include <iostream>
using namespace std;
#include <assert.h>
#include <vector>
#include <time.h>
template<class K, class V>
struct AVLTreeNode{
pair<K,V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;//balance factor
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv),
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_bf(0)
{
}
};
template <class K,class V>
class AVLTree{
private:
typedef AVLTreeNode<K, V> Node;
Node* _root = nullptr;
Node* Copy(Node* root){
//前序遍历拷贝
if(root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_kv);
Copy(root->_left);
Copy(root->_right);
return newRoot;
}
void Destory(Node* root){
if(root == nullptr)
return;
//后序遍历析构
Destory(root->_left);
Destory(root->_right);
delete root;
}
void _InOrder(Node* root){
if(root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " : " << root->_kv.second <<" ";
_InOrder(root->_right);
}
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
return max(_Height(root->_left), _Height(root->_right)) + 1;
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
// 不平衡
if (abs(leftHeight - rightHeight) >= 2)
{
cout << root->_kv.first << endl;
return false;
}
// 顺便检查一下平衡因子是否正确
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << endl;
return false;
}
return _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
public:
//构造函数
AVLTree() = default;
//拷贝函数
AVLTree(const AVLTree<K,V> &t){
_root = Copy(t._root);
}
//赋值运算符重载
AVLTreeNode<K, V>& operator=(const AVLTree<K,V> &t){
swap(_root,t._root);
return *this;
}
//析构函数
~AVLTree(){
Destory(_root);
_root = nullptr;
}
//中序遍历
void InOrder(){
_InOrder(_root);
cout << endl;
}
//查找元素
//只需要传k就能找到v,所以传k就行了
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);
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(kv.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
//更新平衡因子
while(parent){
if(parent->_left == cur)
//插入在左子树 --
parent->_bf--;
else
//插入在右子树 ++
parent->_bf++;
if(parent->_bf == 0){
//1️⃣ 插入后parent的平衡因子 == 0
/*说明parent的平衡因子更新前是1 or-1,插入节点插入矮的那边
parent所在子树的高度不变,不需要继续往上更新*/
break;
}else if (parent->_bf == 1 || parent->_bf == -1){
//插入后parent的平衡因子 == 1/-1
/*说明parent的平衡因子更新前是0,插入节点插入在任意一边
parent所在的子树高度都会变化了,需要继续往上更新*/
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || 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)
{
RotateRL(parent);
}
else
{
RotateLR(parent);
}
break;
}else{
// 理论而言不可能出现这个情况
assert(false);
}
}
return true;
}
//右边高-->左单旋
void RotateL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* pparent = parent->_parent;//检验parent是根还是子树
parent->_right = subRL;
if(subRL)//高度不为0的时候在指向,否则出现空指针指向问题
subRL->_parent = parent;
subR->_left = parent;
parent->_parent = subR;
if(pparent == nullptr){
//说明parent原先是根
_root = subR;
subR->_parent = nullptr;
}else{
//说明parent原先是子树
if(pparent->_left == parent)
pparent->_left = subR;
if(pparent->_right == pparent)
pparent->_right = subR;
subR->_parent = pparent;
}
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
//右旋
void RotateR(Node* parent){
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* pparent = parent->_parent;
parent->_left = subLR;
if(subLR)
subLR->_parent = parent;
subL->_right = parent;
parent->_parent = subL;
if(pparent == nullptr){
_root = subL;
subL->_parent = nullptr;
}else{
if(pparent->_left == parent)
pparent->_left = subL;
if(pparent->_right == parent)
pparent->_right = subL;
subL->_parent = pparent;
}
subL->_bf = parent->_bf = 0;
}
//
void RotateRL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;//维护新插节点在右子树左端的左节点还是右节点
//先右旋
RotateR(parent->_right);
//在左旋
RotateL(parent);
if(bf == 0){
// subRL就是新插节点,无孩子
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}else if(bf == -1){
// 新节点插入在subRL的左端
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}else if (bf == 1){
// 新节点插入在subRL的右端
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else{
assert(false);
}
}
void RotateLR(Node* parent){
Node* subL = parent->_left;
Node* subLR = subL->_right;
int _bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if(_bf == 0){
subL->_bf = 0;
subLR->_bf = 0;
parent = 0;
}else if (_bf == -1){
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}else if (_bf ==1){
subL->_bf = -1;
subLR->_bf = 0;
parent = 0;
}else{
assert(false);
}
}
bool IsBalance()
{
return _IsBalance(_root);
}
int Height()
{
return _Height(_root);
}
int Size()
{
return _Size(_root);
}
};
void TestAVLTree1()
{
//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13};
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<int, int> t1;
for (auto e : a)
{
/*if (e == 4)
{
int i = 0;
}*/
// 1、先看是插入谁导致出现的问题
// 2、打条件断点,画出插入前的树
// 3、单步跟踪,对比图一一分析细节原因
t1.Insert({e,e});
if(e == 6){
int a = 1;
}
cout <<"Insert:"<< e<<"->"<< t1.IsBalance() << endl;
}
t1.InOrder();
cout << t1.IsBalance() << endl;
}
void TestAVLTree2()
{
const int N = 1000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand()+i);
//cout << v.back() << endl;
}
size_t begin2 = clock();
AVLTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
}
size_t end2 = clock();
cout << "Insert:" << end2 - begin2 << endl;
//cout << t.IsBalance() << endl;
cout << "Height:" << t.Height() << endl;
cout << "Size:" << t.Size() << endl;
size_t begin1 = clock();
// 确定在的值
for (auto e : v)
{
t.Find(e);
}
// 随机值
/*for (size_t i = 0; i < N; i++)
{
t.Find((rand() + i));
}*/
size_t end1 = clock();
cout << "Find:" << end1 - begin1 << endl;
}