文章目录
- 一、AVL树的介绍
- 二、AVL树的实现
- 1、基本框架
- 2、查找
- 3、插入
- 4、删除
- 5、测试
- 6、总代码
- 三、AVL树的性能
一、AVL树的介绍
1、概念
AVL树(Adelson-Velsky and Landis Tree)是一种自平衡的二叉搜索树。它得名于其发明者G. M. Adelson-Velsky和E. M. Landis。在AVL树中,任何节点的两个子树的高度最大差别为1,这保证了树的平衡性,从而避免了在极端情况下(如数据有序或接近有序时)二叉搜索树退化为链表,导致操作效率低下的问题。
2、特点
(1)平衡性:AVL树通过维护每个节点的平衡因子(左子树高度与右子树高度之差)来保持树的平衡。平衡因子的值只能是-1、0或1。如果某个节点的平衡因子绝对值大于1,那么该树就失去了平衡,需要通过旋转操作来重新平衡。
(2)旋转操作:当AVL树失去平衡时,会触发旋转操作来恢复平衡。旋转操作主要有四种:右旋(单旋)、左旋(单旋)、右-左双旋和左-右双旋。这些旋转操作通过改变树中节点的链接关系来降低树的高度,从而保持树的平衡。
(3) 高效的查找、插入和删除操作:由于AVL树保持了平衡,其查找、插入和删除操作的时间复杂度都能保持在O(log
n)的范围内,其中n是树中节点的数量。这使得AVL树在处理大量数据时能够保持高效的性能。(4)空间开销:与普通的二叉搜索树相比,AVL树需要额外的空间来存储每个节点的平衡因子。这增加了树的空间开销,但在大多数情况下,这种开销是可以接受的。
3、应用场景
AVL树广泛应用于需要频繁插入、删除和查找操作的场景,如数据库索引、文件系统的目录结构、实时数据更新等。在这些场景中,AVL树能够保持高效的性能,确保数据处理的快速响应。
4、缺点
尽管AVL树具有许多优点,但它也有一些缺点。例如,在每次插入或删除节点后都需要进行平衡检查和可能的旋转操作,这增加了操作的复杂度。此外,与红黑树等其他自平衡二叉搜索树相比,AVL树在插入和删除操作时可能需要更多的旋转操作来保持平衡。因此,在某些特定场景下,红黑树可能比AVL树更受欢迎。然而,在需要高度平衡的场合,AVL树仍然是一个非常好的选择。
二、AVL树的实现
1、基本框架
树节点:
//树节点
template<class K, class V>
struct AVLTreeNode
{
//构造函数
AVLTreeNode(const pair<K, V>& val = pair<K, V>())
: _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _val(val)
, _bf(0)
{}
AVLTreeNode<K,V>* _left; //左孩子指针
AVLTreeNode<K, V>* _right; //右孩子指针
AVLTreeNode<K, V>* _parent; //父亲指针
pair<K, V> _val; //数据
int _bf; //节点的平衡因子 -> 右子树高度 - 左子树高度
};
AVL树类:
template<class K, class V>
class AVLTree
{
//树节点
typedef AVLTreeNode<K, V> Node;
private:
//根节点
Node* _root = nullptr;
};
2、查找
AVL树的查找操作与普通的二叉搜索树(BST)的查找操作非常相似。由于AVL树本身就是一种二叉搜索树,所以它可以利用二叉搜索树的性质来高效地查找元素。在AVL树中查找元素的过程基本上不涉及树的平衡操作(如旋转),只是简单地利用节点的值和树的结构来定位目标元素。
步骤:从根节点(cur)开始,通过判断key与当前节点key的大小来决定是去左子树还是右子树找该值,循环迭代(cur)上述过程直到找到相同的key后返回该节点,否则就返回空。
//查找
Node* Find(const K& key)
{
//从根节点开始
Node* cur = _root;
while (cur)
{
//大了就去左子树中搜索
if (cur->_val.first > key)
{
cur = cur->_left;
}
//小了就去右子树中搜索
else if (cur->_val.first < key)
{
cur = cur->_right;
}
else
{
//找到返回当前节点
return cur;
}
}
return nullptr;
}
3、插入
AVL树的插入操作在二叉搜索树(BST)的插入基础上增加了维护树平衡的步骤。AVL树是一种自平衡的二叉搜索树,其中任何节点的两个子树的高度最大差别为1。
在插入后需要更新平衡因子,插入左子树节点时,父节点的平衡因子-1,插入右子树节点时,父节点的平衡因子+1,当更新后的父节点的平衡因子为0就不需要向上更新了(说明插入之前是-1或者1,插入节点后不会影响到上面的节点的平衡因子),当平衡因子(右子树高度 - 左子树高度)为1或者-1时需要向上更新平衡因子(说明插入之前为0,插入后会影响到上面节点的平衡因子),当为2或者-2时说明该树失去平衡了,需要旋转来调整高度,旋转之后的高度就平衡了不需要向上更新。
(1)插入一个元素使左子树高度大于右子树且失去平衡(单纯一边高(L和parent平衡因子同号)) ------- 右旋转
平衡因子:根据图分析,旋转后parent和L都为0
// 右单旋
void RotateR(Node* parent)
{
//左节点
Node* L = parent->_left;
//左子树右边第一个节点
Node* Lr = L->_right;
//parent的父亲
Node* pparent = parent->_parent;
//连接过程
L->_right = parent;
parent->_parent = L;
//该节点可能为空
if (Lr)
{
Lr->_parent = parent;
}
parent->_left = Lr;
//更新L的父节点
L->_parent = pparent;
//是根的情况
if (pparent == nullptr)
{
_root = L;
}
else
{
if (parent == pparent->_left) pparent->_left = L;
else pparent->_right = L;
}
//更新后平衡因子都为0
parent->_bf = L->_bf = 0;
}
(2)插入一个元素使右子树高度大于左子树且失去平衡(单纯一边高(R和parent平衡因子同号)) ------- 左旋转
平衡因子:根据图分析,旋转后parent和R都为0
//左旋转
void RotateL(Node* parent)
{
//右边第一个节点
Node* R = parent->_right;
//右子树第一个左节点
Node* Rl = R->_left;
//父节点
Node* pparent = parent->_parent;
//连接过程
parent->_right = Rl;
if (Rl)
{
Rl->_parent = parent;
}
R->_left = parent;
//更新parent的父节点
parent->_parent = R;
//更新R的父节点
R->_parent = pparent;
//是根的情况
if (nullptr == pparent)
{
_root = R;
}
else
{
if (pparent->_left == parent) pparent->_left = R;
else pparent->_right = R;
}
//更新平衡因子
parent->_bf = R->_bf = 0;
}
(3)插入一个元素使左子树高度大于右子树且失去平衡(不单纯一边高(L和parent平衡因子异号)) ------- 左右旋转
平衡因子:根据图分析(上面只有Lr = 1 的情况,Lr = 0,或者Lr = -1的情况也按上图的方式推导),更新前Lr的平衡因子为1时更新后L的平衡因子为-1、parent和Lr为0,更新前Lr的平衡因子为-1时更新后parent平衡因子为1、L和Lr平衡因子为0,更新前Lr的平衡因子为0时,L、Lr、parent平衡因子都为0。
// 左右双旋
void RotateLR(Node* parent)
{
Node* L = parent->_left;
Node* Lr = L->_right;
//先保存Lr的平衡因子,因为旋转之后Lr的平衡因子会变
int bf = Lr->_bf;
//左右双旋
RotateL(L);
RotateR(parent);
//更新平衡因子
if (bf == -1)
{
parent->_bf = 1;
L->_bf = 0;
Lr->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
L->_bf = -1;
Lr->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
L->_bf = 0;
Lr->_bf = 0;
}
else
assert(false);
}
(4)插入一个元素使右子树高度大于左子树且失去平衡(不单纯一边高(R和parent平衡因子异号)) ------- 右左旋转
平衡因子:根据图分析(上面只有Rl = 1 的情况,Rl = 0,或者Rl = -1的情况也按上图的方式推导),更新前Rl的平衡因子为-1时更新后R的平衡因子为-1、parent和Rl为0,更新前Rl的平衡因子为1时更新后parent平衡因子为-1、R和Rl平衡因子为0,更新前Rl的平衡因子为0时,R、Rl、parent平衡因子都为0。
// 右左双旋
void RotateRL(Node* parent)
{
Node* R = parent->_right;
Node* Rl = R->_left;
//先保存平衡因子
int bf = Rl->_bf;
//右左双旋
RotateR(R);
RotateL(parent);
//更新平衡因子
if (bf == 1)
{
parent->_bf = -1;
R->_bf = 0;
Rl->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
R->_bf = 1;
Rl->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
R->_bf = 0;
Rl->_bf = 0;
}
else
assert(false);
}
(5)根据插入节点(参考搜索二叉树)+更新平衡因子完成插入
//插入
bool Insert(const pair<K, V>& val)
{
//找到放val的位置
Node* cur = _root;
//作为前驱指针,与val节点连接
Node* precursor = nullptr;
while (cur)
{
//向左
if (cur->_val.first > val.first)
{
precursor = cur;
cur = cur->_left;
}
//向右
else if (cur->_val.first < val.first)
{
precursor = cur;
cur = cur->_right;
}
//存在相同的值
else
{
return false;
}
}
//插入新节点
cur = new Node(val);
//不存在根节点,作为根节点
if (precursor == nullptr) _root = cur;
//连接在前驱指针左侧
else if (precursor->_val.first > val.first)
{
cur->_parent = precursor;
precursor->_left = cur;
}
//连接在前驱指针右侧
else
{
cur->_parent = precursor;
precursor->_right = cur;
}
//更新平衡因子
while (precursor)
{
if (precursor->_left == cur)
precursor->_bf--;
else
precursor->_bf++;
//为0说明平衡了,不需要再更新了
if (precursor->_bf == 0)
break;
//出现异常需要更新平衡因子,更新完就可以
else if (precursor->_bf == 2 || precursor->_bf == -2)
{
if (precursor->_bf == 2 && cur->_bf == 1)
{
RotateL(precursor);
}
else if (precursor->_bf == -2 && cur->_bf == -1)
{
RotateR(precursor);
}
else if (precursor->_bf == 2 && cur->_bf == -1)
{
RotateRL(precursor);
}
else if (precursor->_bf == -2 && cur->_bf == 1)
{
RotateLR(precursor);
}
else
{
assert(false);
}
//更新完平衡了不需要向上更新
break;
}
//进行迭代(向上更新)
cur = precursor;
precursor = precursor->_parent;
}
return true;
}
4、删除
AVL树删除操作是一个复杂但关键的过程,因为它需要在删除节点后重新调整树的结构以保持其平衡性。AVL树是一种自平衡的二叉搜索树,其中任何节点的两个子树的高度最大差别为1。
在删除后需要更新平衡因子,删除左子树节点时,父节点的平衡因子+1,删除右子树节点时,父节点的平衡因子-1,当更新后的父节点的平衡因子为-1 或者 1就不需要向上更新了(说明删除之前0,删除节点后不会影响到上面的节点的平衡因子),当平衡因子0时需要向上更新平衡因子(说明删除之前为1或者-1,删除后会影响到上面节点的平衡因子),当为2或者-2时说明该树失去平衡了,需要旋转来调整高度,旋转之后当前父节点还是0的话还要继续向上更新,直到更新为-1或者1时就结束更新。
因为复用插入的旋转操作,所以在删除元素后有一些平衡因子在旋转过程中没有正确更新,此时我们就要在旋转完后再次更新。
(1)删除后,右边高了,当R = 0,或者 R = 1 时进行 — 左单旋
平衡因子:当R的平衡因子为0时,parent的平衡因子需要修改为1,R的平衡因子修改为-1,当R的平衡因子为1时(也是按上图方式推导),parent、R平衡因子都为0。
(2)删除后,左边高了,当L = 0,或者 L = -1 时进行 — 右单旋
平衡因子:当L的平衡因子为0时,parent的平衡因子需要修改为-1,L的平衡因子修改为-1,当L的平衡因子为-1时(也是按上图方式推导),parent、L平衡因子都为0。
(3)删除后,右边高了,并且出现异号 — 右左双旋
平衡因子:与插入时使用的右左双旋一样。
(4)删除后,左边高了,并且出现异号 — 左右双旋
平衡因子:与插入时使用的左右双旋一样。
(5)使用删除操作(参考搜索二叉树的删除)+旋转完成删除
//删除
bool Erase(const K& key)
{
//从根节点开始搜索
Node* cur = _root;
//作为cur的前驱指针
Node* precursor = nullptr;
//搜索查找
while (cur)
{
if (cur->_val.first > key)
{
precursor = cur;
cur = cur->_left;
}
else if (cur->_val.first < key)
{
precursor = cur;
cur = cur->_right;
}
else
break;
}
//找不到
if (cur == nullptr) return false;
//假设cur左右节点都存在,找右边最小值替换
if (cur->_left != nullptr && cur->_right != nullptr)
{
Node* tmp1 = cur->_right;
Node* tmp2 = nullptr;
while (tmp1->_left)
{
tmp2 = tmp1;
tmp1 = tmp1->_left;
}
cur->_val = tmp1->_val;
//tmp1左边没有节点,自己就是最小的节点
if (tmp2 == nullptr)
{
precursor = cur;
cur = tmp1;
}
else
{
cur = tmp1;
precursor = tmp2;
}
}
//假设左边为空和左右节点都为空
int sign = 0;//左边为-1,右边为1
Node* deletecur = cur;
if (cur->_left == nullptr)
{
左边为空,父节点为空,cur为根节点,让cur->_riggt做为根,直接结束就行了(cur->_riggt本身为平衡树)
if (precursor == nullptr)
{
_root = cur->_right;
delete deletecur;
return true;
}
else
{
if (precursor->_left == cur)
{
precursor->_left = cur->_right;
if (cur->_right == nullptr) sign = -1;
}
else
{
precursor->_right = cur->_right;
if (cur->_right == nullptr) sign = 1;
}
}
cur = cur->_right;
}
//假设右边为空
else
{
//右边为空,父节点为空,cur为根节点,让cur->_left做为根,直接结束就行了(cur->_left本身为平衡树)
if (precursor == nullptr)
{
_root = cur->_left;
delete deletecur;
return true;
}
else
{
if (precursor->_left == cur)
precursor->_left = cur->_left;
else
precursor->_right = cur->_left;
}
cur = cur->_left;
}
//更新平衡因子
while (precursor)
{
//cur出现空的情况
if (cur == nullptr)
{
if (sign == -1)
precursor->_bf++;
else
precursor->_bf--;
}
else if (precursor->_left == cur)
precursor->_bf++;
else
precursor->_bf--;
if (precursor->_bf == -1 || precursor->_bf == 1)
break;
else if (precursor->_bf == -2 || precursor->_bf == 2)
{
//右边高了左单旋
if (precursor->_bf == 2 && (precursor->_right->_bf == 0 || precursor->_right->_bf == 1))
{
//R会做为新的precursor先保存
Node* R = precursor->_right;
int bf = precursor->_right->_bf;
RotateL(precursor);
//在旋转后的平衡因子不符合预期,需要更新
if (bf == 0)
{
precursor->_bf = 1;
R->_bf = -1;
}
//更新
precursor = R;
}
//左边高了右单旋
else if (precursor->_bf == -2 && (precursor->_left->_bf == 0 || precursor->_left->_bf == -1))
{
//L会做为新的precursor先保存
Node* L = precursor->_left;
int bf = L->_bf;
RotateR(precursor);
//在旋转后的平衡因子不符合预期,需要更新
if (bf == 0)
{
precursor->_bf = -1;
L->_bf = 1;
}
//更新
precursor = L;
}
else if (precursor->_bf == 2 && precursor->_right->_bf == -1)
{
//L会做为新的precursor先保存
Node* R = precursor->_right;
Node* L = R->_left;
RotateRL(precursor);
//更新
precursor = L;
}
else if (precursor->_bf == -2 && precursor->_left->_bf== 1)
{
//R会做为新的precursor先保存
Node* L = precursor->_left;
Node* R = L->_right;
RotateLR(precursor);
//更新
precursor = R;
}
else
assert(false);
//旋转完precursor更新了再次判断是否平衡了
if (precursor->_bf == -1 || precursor->_bf == 1) break;
}
//进行迭代
cur = precursor;
precursor = precursor->_parent;
}
delete deletecur;
return true;
}
5、测试
通过两颗子树的高度差是否大于1和判断高度差是否与这颗根节点平衡因子相等来判断是否为AVL树。
//判断是否为平衡树
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
//验证是否是平衡树
bool _IsBalanceTree(Node* root)
{
if (root == nullptr) return true;
int l = _Height(root->_left);
int r = _Height(root->_right);
int buff = r - l;
if (root->_bf != buff || buff > 1 || buff < -1)
return false;
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
//求树的高度
int _Height(Node* root)
{
if (root == nullptr) return 0;
int l = _Height(root->_left);
int r = _Height(root->_right);
return max(l, r) + 1;
}
void test()
{
AVLTree<int, int> a;
vector<int> arr;
for (int i = 0; i < 10000; i++)
{
int m = rand() + i;
arr.push_back(m);
}
//插入10000个随机数
for (auto e :arr)
{
a.Insert({ e,e });
if (a.IsBalanceTree())
cout << "是AVL树" << endl;
else
assert(false);
}
//再全部删除
for (auto e : arr)
{
a.Erase(e);
if (a.IsBalanceTree())
cout << "是AVL树" << endl;
else
assert(false);
}
}
在插入和删除过程中没有报错,说明该树是AVL树。
6、总代码
#pragma once
#include<iostream>
#include<string>
#include<cassert>
#include<queue>
#include<vector>
using namespace std;
//树节点
template<class K, class V>
struct AVLTreeNode
{
//构造函数
AVLTreeNode(const pair<K, V>& val = pair<K, V>())
: _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _val(val)
, _bf(0)
{}
AVLTreeNode<K,V>* _left; //左孩子指针
AVLTreeNode<K, V>* _right; //右孩子指针
AVLTreeNode<K, V>* _parent; //父亲指针
pair<K, V> _val; //数据
int _bf; //节点的平衡因子 -> 右子树高度 - 左子树高度
};
template<class K, class V>
class AVLTree
{
//树节点
typedef AVLTreeNode<K, V> Node;
public:
~AVLTree()
{
DeleteTree(_root);
}
//插入
bool Insert(const pair<K, V>& val)
{
//找到放val的位置
Node* cur = _root;
//作为前驱指针,与val节点连接
Node* precursor = nullptr;
while (cur)
{
//向左
if (cur->_val.first > val.first)
{
precursor = cur;
cur = cur->_left;
}
//向右
else if (cur->_val.first < val.first)
{
precursor = cur;
cur = cur->_right;
}
//存在相同的值
else
{
return false;
}
}
//插入新节点
cur = new Node(val);
//不存在根节点,作为根节点
if (precursor == nullptr) _root = cur;
//连接在前驱指针左侧
else if (precursor->_val.first > val.first)
{
cur->_parent = precursor;
precursor->_left = cur;
}
//连接在前驱指针右侧
else
{
cur->_parent = precursor;
precursor->_right = cur;
}
//更新平衡因子
while (precursor)
{
if (precursor->_left == cur)
precursor->_bf--;
else
precursor->_bf++;
//为0说明平衡了,不需要再更新了
if (precursor->_bf == 0)
break;
//出现异常需要更新平衡因子,更新完就可以
else if (precursor->_bf == 2 || precursor->_bf == -2)
{
if (precursor->_bf == 2 && cur->_bf == 1)
{
RotateL(precursor);
}
else if (precursor->_bf == -2 && cur->_bf == -1)
{
RotateR(precursor);
}
else if (precursor->_bf == 2 && cur->_bf == -1)
{
RotateRL(precursor);
}
else if (precursor->_bf == -2 && cur->_bf == 1)
{
RotateLR(precursor);
}
else
{
assert(false);
}
//更新完平衡了不需要向上更新
break;
}
//进行迭代(向上更新)
cur = precursor;
precursor = precursor->_parent;
}
return true;
}
//查找
Node* Find(const K& key)
{
//从根节点开始
Node* cur = _root;
while (cur)
{
//大了就去左子树中搜索
if (cur->_val.first > key)
{
cur = cur->_left;
}
//小了就去右子树中搜索
else if (cur->_val.first < key)
{
cur = cur->_right;
}
else
{
//找到返回当前节点
return cur;
}
}
return nullptr;
}
//删除
bool Erase(const K& key)
{
//从根节点开始搜索
Node* cur = _root;
//作为cur的前驱指针
Node* precursor = nullptr;
//搜索查找
while (cur)
{
if (cur->_val.first > key)
{
precursor = cur;
cur = cur->_left;
}
else if (cur->_val.first < key)
{
precursor = cur;
cur = cur->_right;
}
else
break;
}
//找不到
if (cur == nullptr) return false;
//假设cur左右节点都存在,找右边最小值替换
if (cur->_left != nullptr && cur->_right != nullptr)
{
Node* tmp1 = cur->_right;
Node* tmp2 = nullptr;
while (tmp1->_left)
{
tmp2 = tmp1;
tmp1 = tmp1->_left;
}
cur->_val = tmp1->_val;
//tmp1左边没有节点,自己就是最小的节点
if (tmp2 == nullptr)
{
precursor = cur;
cur = tmp1;
}
else
{
cur = tmp1;
precursor = tmp2;
}
}
//假设左边为空和左右节点都为空
int sign = 0;//左边为-1,右边为1
Node* deletecur = cur;
if (cur->_left == nullptr)
{
左边为空,父节点为空,cur为根节点,让cur->_riggt做为根,直接结束就行了(cur->_riggt本身为平衡树)
if (precursor == nullptr)
{
_root = cur->_right;
delete deletecur;
return true;
}
else
{
if (precursor->_left == cur)
{
precursor->_left = cur->_right;
if (cur->_right == nullptr) sign = -1;
}
else
{
precursor->_right = cur->_right;
if (cur->_right == nullptr) sign = 1;
}
}
cur = cur->_right;
}
//假设右边为空
else
{
//右边为空,父节点为空,cur为根节点,让cur->_left做为根,直接结束就行了(cur->_left本身为平衡树)
if (precursor == nullptr)
{
_root = cur->_left;
delete deletecur;
return true;
}
else
{
if (precursor->_left == cur)
precursor->_left = cur->_left;
else
precursor->_right = cur->_left;
}
cur = cur->_left;
}
//更新平衡因子
while (precursor)
{
//cur出现空的情况
if (cur == nullptr)
{
if (sign == -1)
precursor->_bf++;
else
precursor->_bf--;
}
else if (precursor->_left == cur)
precursor->_bf++;
else
precursor->_bf--;
if (precursor->_bf == -1 || precursor->_bf == 1)
break;
else if (precursor->_bf == -2 || precursor->_bf == 2)
{
//右边高了左单旋
if (precursor->_bf == 2 && (precursor->_right->_bf == 0 || precursor->_right->_bf == 1))
{
//R会做为新的precursor先保存
Node* R = precursor->_right;
int bf = precursor->_right->_bf;
RotateL(precursor);
//在旋转后的平衡因子不符合预期,需要更新
if (bf == 0)
{
precursor->_bf = 1;
R->_bf = -1;
}
//更新
precursor = R;
}
//左边高了右单旋
else if (precursor->_bf == -2 && (precursor->_left->_bf == 0 || precursor->_left->_bf == -1))
{
//L会做为新的precursor先保存
Node* L = precursor->_left;
int bf = L->_bf;
RotateR(precursor);
//在旋转后的平衡因子不符合预期,需要更新
if (bf == 0)
{
precursor->_bf = -1;
L->_bf = 1;
}
//更新
precursor = L;
}
else if (precursor->_bf == 2 && precursor->_right->_bf == -1)
{
//L会做为新的precursor先保存
Node* R = precursor->_right;
Node* L = R->_left;
RotateRL(precursor);
//更新
precursor = L;
}
else if (precursor->_bf == -2 && precursor->_left->_bf== 1)
{
//R会做为新的precursor先保存
Node* L = precursor->_left;
Node* R = L->_right;
RotateLR(precursor);
//更新
precursor = R;
}
else
assert(false);
//旋转完precursor更新了再次判断是否平衡了
if (precursor->_bf == -1 || precursor->_bf == 1) break;
}
//进行迭代
cur = precursor;
precursor = precursor->_parent;
}
delete deletecur;
return true;
}
//判断是否为平衡树
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
private:
// 右单旋
void RotateR(Node* parent)
{
//左节点
Node* L = parent->_left;
//左子树右边第一个节点
Node* Lr = L->_right;
//parent的父亲
Node* pparent = parent->_parent;
//连接过程
L->_right = parent;
parent->_parent = L;
//该节点可能为空
if (Lr)
{
Lr->_parent = parent;
}
parent->_left = Lr;
//更新L的父节点
L->_parent = pparent;
//是根的情况
if (pparent == nullptr)
{
_root = L;
}
else
{
if (parent == pparent->_left) pparent->_left = L;
else pparent->_right = L;
}
//更新后平衡因子都为0
parent->_bf = L->_bf = 0;
}
//左旋转
void RotateL(Node* parent)
{
//右边第一个节点
Node* R = parent->_right;
//右子树第一个左节点
Node* Rl = R->_left;
//父节点
Node* pparent = parent->_parent;
//连接过程
parent->_right = Rl;
if (Rl)
{
Rl->_parent = parent;
}
R->_left = parent;
//更新parent的父节点
parent->_parent = R;
//更新R的父节点
R->_parent = pparent;
//是根的情况
if (nullptr == pparent)
{
_root = R;
}
else
{
if (pparent->_left == parent) pparent->_left = R;
else pparent->_right = R;
}
//更新平衡因子
parent->_bf = R->_bf = 0;
}
// 右左双旋
void RotateRL(Node* parent)
{
Node* R = parent->_right;
Node* Rl = R->_left;
//先保存平衡因子
int bf = Rl->_bf;
//右左双旋
RotateR(R);
RotateL(parent);
//更新平衡因子
if (bf == 1)
{
parent->_bf = -1;
R->_bf = 0;
Rl->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
R->_bf = 1;
Rl->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
R->_bf = 0;
Rl->_bf = 0;
}
else
assert(false);
}
// 左右双旋
void RotateLR(Node* parent)
{
Node* L = parent->_left;
Node* Lr = L->_right;
//先保存Lr的平衡因子,因为旋转之后Lr的平衡因子会变
int bf = Lr->_bf;
//左右双旋
RotateL(L);
RotateR(parent);
//更新平衡因子
if (bf == -1)
{
parent->_bf = 1;
L->_bf = 0;
Lr->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
L->_bf = -1;
Lr->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
L->_bf = 0;
Lr->_bf = 0;
}
else
assert(false);
}
//求树的高度
int _Height(Node* root)
{
if (root == nullptr) return 0;
int l = _Height(root->_left);
int r = _Height(root->_right);
return max(l, r) + 1;
}
//验证是否是平衡树
bool _IsBalanceTree(Node* root)
{
if (root == nullptr) return true;
int l = _Height(root->_left);
int r = _Height(root->_right);
int buff = r - l;
if (root->_bf != buff || buff > 1 || buff < -1)
return false;
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
void DeleteTree(Node* root)
{
if (root == nullptr)
return;
DeleteTree(root->_left);
DeleteTree(root->_right);
delete root;
}
//根节点
Node* _root = nullptr;
};
三、AVL树的性能
1、搜索性能分析
时间复杂度:O(log n)
(1) AVL树作为二叉搜索树的一种,其搜索操作与普通的二叉搜索树相同。从根节点开始,根据要搜索的值与节点值的大小关系,决定是向左子树搜索还是向右子树搜索,直到找到目标节点或搜索到叶子节点为止。
(2)由于AVL树的高度被限制在O(log n)以内(其中n是树中节点的数量),因此搜索操作的时间复杂度也是O(log n)。
2、插入性能分析
时间复杂度:O(log n)
(1)插入操作首先执行普通的二叉搜索树插入操作,找到新节点应该插入的位置。 插入新节点后,从该节点开始向上遍历,更新所有受影响节点的平衡因子。
(2)如果某个节点的平衡因子变为2或-2(即左右子树高度差超过1),则需要进行旋转操作来恢复平衡。旋转操作包括单旋转(左单旋、右单旋)和双旋转(左右双旋、右左双旋)。
(3)由于旋转操作只在从插入节点到根节点的路径上进行,且路径长度不超过树的高度,因此插入操作的时间复杂度也是O(log n)。
3、删除性能分析
时间复杂度:O(log n)
(1)删除操作首先找到要删除的节点。
(2)如果要删除的节点有两个子节点,则通常使用其右子树中的最小节点(或左子树中的最大节点)来替换它,并删除那个最小(或最大)节点。
(3)删除节点后,从该节点开始向上遍历,更新所有受影响节点的平衡因子。
(4)如果某个节点的平衡因子变为2或-2,则需要进行旋转操作来恢复平衡。旋转操作的类型与插入操作类似,包括单旋转和双旋转。
(5)同样地,由于旋转操作只在从删除节点到根节点的路径上进行,且路径长度不超过树的高度,因此删除操作的时间复杂度也是O(log n)。
4、总结
AVL树通过保持树的平衡性,使得搜索、插入和删除操作的时间复杂度都能保持在O(log
n)的水平。这种高效的性能使得AVL树在需要频繁进行动态修改操作的数据结构中非常有用,如数据库索引、文件系统等。然而,需要注意的是,AVL树在每次插入或删除操作后都需要进行平衡调整,这可能会增加一些额外的开销。但在大多数情况下,这种开销是可以接受的,因为AVL树提供了比未平衡的二叉搜索树更好的性能保证。