1. AVL树的基本概念与原理
AVL树是一种自平衡的二叉搜索树,它以两位发明者G.M. Adelson-Velsky和E.M. Landis的名字命名。其核心特性是每个节点的左右子树的高度差(也称为平衡因子)不能超过1。当插入或删除节点时,AVL树会通过旋转操作来维持这一平衡,从而确保在最坏情况下,树的高度依然保持在O(log n),从而使搜索、插入和删除操作的时间复杂度都能保持在O(log n)以内。
平衡因子是AVL树中的关键概念。对于每个节点,其平衡因子定义为左子树的高度减去右子树的高度。因此,平衡因子的值只能是-1、0或1。当节点的平衡因子超出这个范围时,AVL树会通过旋转操作恢复平衡。
二叉搜索树(BST)的特性是每个节点的左子节点都比该节点小,右子节点都比该节点大。然而,普通的二叉搜索树在节点插入时可能会导致高度不平衡,例如,如果按顺序插入一系列递增的元素,树可能会退化成一条链表。AVL树通过严格的平衡条件避免了这种情况,因此具有更好的性能。
例如,在一个二叉搜索树中,节点的分布可能非常不均匀,某一侧的子树过深,会导致某些操作的效率降低。而AVL树通过平衡左右子树的高度差,确保树的高度保持在O(log n)级别,从而保证操作的高效性。
在AVL树中,每次插入或删除节点后,都会通过递归地计算每个祖先节点的平衡因子,并对那些不平衡的节点进行旋转修正。根据不平衡的情况,有四种不同的旋转方式:左旋、右旋、左右双旋和右左双旋。下一节将详细讨论这些旋转操作。
1.1 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; //balance factor
AVLTreeNode(const pair<K, V>& _kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(_kv)
, _bf(0)
{}
};
1.2 AVL树的插入
在插入节点时,首先需要按照二叉搜索树(BST)的规则找到插入位置。然后,通过向上追溯父节点,逐步调整平衡因子,并在需要时进行旋转修复。以下是插入的详细步骤:
- 从根节点开始,通过键值大小比较,决定是向左子树还是右子树递归插入节点。
- 一旦找到合适的插入位置,将新节点插入树中,同时更新其父节点的指针。
- 接下来,从插入的节点开始,向上追溯,更新每个祖先节点的平衡因子。如果平衡因子变为2或-2,表明该节点的子树出现不平衡,需要进行旋转操作来恢复平衡。
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->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
// 向上更新平衡因子并判断是否需要旋转
while (parent) {
if (cur == parent->_left) {
parent->_bf--; // 左子树增加,平衡因子减小
} else {
parent->_bf++; // 右子树增加,平衡因子增加
}
// 如果父节点平衡因子为0,树依然平衡,停止更新
if (parent->_bf == 0) {
break;
} else if (parent->_bf == 1 || parent->_bf == -1) {
// 继续向上更新
cur = parent;
parent = parent->_parent;
} else if (parent->_bf == 2 || parent->_bf == -2) {
// 出现不平衡,需要进行旋转
if (parent->_bf == -2 && cur->_bf == -1) {
RotateR(parent); // 右旋
} else if (parent->_bf == 2 && cur->_bf == 1) {
RotateL(parent); // 左旋
} else if (parent->_bf == 2 && cur->_bf == -1) {
RotateRL(parent); // 先右后左双旋
} else if (parent->_bf == -2 && cur->_bf == 1) {
RotateLR(parent); // 先左后右双旋
}
break;
}
}
return true;
}
1.3AVL树的旋转
3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
假如以 pParent 为根的子树不平衡,即 pParent 的平衡因子为 2 或者 -2 ,分以下情况考虑1. pParent 的平衡因子为 2 ,说明 pParent 的右子树高,设 pParent 的右子树的根为 pSubR当 pSubR 的平衡因子为 1 时,执行左单旋当 pSubR 的平衡因子为 -1 时,执行右左双旋2. pParent 的平衡因子为 -2 ,说明 pParent 的左子树高,设 pParent 的左子树的根为 pSubL当 pSubL 的平衡因子为 -1 是,执行右单旋当 pSubL 的平衡因子为 1 时,执行左右双旋旋转完成后,原 pParent 为根的子树个高度降低,已经平衡,不需要再向上更新。
2. AVL树的时间复杂度与性能分析
1. AVL树的时间复杂度
AVL树是一种自平衡的二叉搜索树(BST),通过在插入和删除节点时进行旋转操作,确保树的高度始终保持在O(log n)的级别。因此,AVL树在最坏情况下的时间复杂度与完全平衡的二叉搜索树相同。我们具体来看各类操作的时间复杂度。
1.1 查找操作的时间复杂度
在AVL树中,查找操作与普通的二叉搜索树相同,都是基于键值的大小,递归地或迭代地沿着树从根节点向下查找目标节点。由于AVL树通过自平衡特性,始终保证树的高度为O(log n),因此查找操作的时间复杂度为:
- 查找时间复杂度:O(log n)
不论是在最坏情况下,还是平均情况下,AVL树的查找操作时间复杂度都能够保持在O(log n),这是因为AVL树严格限制了树的高度不会退化为线性结构。
1.2 插入操作的时间复杂度
AVL树的插入操作分为两步:首先按照二叉搜索树的规则找到插入位置,其次是向上更新平衡因子并根据需要进行旋转操作。寻找插入位置的过程与查找操作相同,时间复杂度为O(log n)。旋转修复最多涉及O(1)次旋转,因此插入操作的时间复杂度为:
- 插入时间复杂度:O(log n)
由于旋转操作仅涉及局部调整,不需要遍历整棵树,因此即便需要进行旋转,插入操作的时间复杂度也保持在O(log n)。
1.3 删除操作的时间复杂度
AVL树的删除操作与插入操作类似,首先按照二叉搜索树的规则找到并删除目标节点,然后向上更新平衡因子并执行旋转操作。由于删除操作可能影响多个祖先节点,需要逐层更新平衡因子并进行旋转修复,但旋转操作依旧是局部的,最多进行O(1)次旋转,因此删除操作的时间复杂度也为:
- 删除时间复杂度:O(log n)
总体来看,AVL树的查找、插入和删除操作的时间复杂度在最坏情况下都能够维持在O(log n),这是因为AVL树通过旋转操作控制了树的高度,防止其退化成线性结构。
2. AVL树的性能分析
2.1 AVL树的平衡性优势
相比于普通的二叉搜索树(BST),AVL树的最大优势在于其自平衡特性。普通BST在插入元素时,容易出现高度不平衡的情况,尤其是在顺序插入元素时,树可能会退化成链表结构,从而导致操作的时间复杂度退化为O(n)。而AVL树通过严格的平衡因子约束,保证了树的高度总是接近O(log n),大大提高了查找、插入和删除操作的效率。
例如,在普通BST中,如果连续插入递增的元素,树可能会变成如下形态:
1
\
2
\
3
\
4
\
5
在这种情况下,查找元素5的时间复杂度为O(n),性能急剧下降。而在AVL树中,即便插入顺序元素,树也会通过旋转保持平衡,避免退化成线性结构。
2.2 AVL树的空间开销
虽然AVL树的平衡性带来了时间复杂度的优势,但它也付出了一定的空间开销。每个节点除了需要存储键值对外,还需要额外存储一个平衡因子(_bf
),用于表示节点的平衡状态。相比于普通的二叉搜索树,AVL树的每个节点都增加了一个额外的整数字段,用于维护平衡因子。
然而,相比于AVL树带来的性能提升,这部分额外的空间开销是非常小的,尤其在大多数应用场景下,AVL树的平衡性能够大幅提升整体系统的性能。
2.3 AVL树与其他自平衡树的比较
除了AVL树,其他常见的自平衡二叉树包括红黑树(Red-Black Tree)和Splay树。与这些树相比,AVL树具有以下特点:
- AVL树 vs 红黑树:
- AVL树比红黑树更“严格”地保持平衡,因此查找操作的时间复杂度通常比红黑树更优。
- 然而,由于AVL树的旋转次数比红黑树更多,因此在频繁插入和删除操作的场景下,红黑树可能表现更优,因为红黑树的旋转操作较少。
- AVL树 vs Splay树:
- Splay树是一种通过“自我调整”的方式来保持平衡的树结构。与AVL树不同,Splay树在每次查找、插入和删除操作时,都会通过一系列旋转将最近访问的节点移动到根节点,因此在局部性强的操作场景中,Splay树的性能较好。
- 然而,在全局性能上,AVL树的时间复杂度更稳定,适用于更广泛的应用场景。
3. AVL树的适用场景
由于AVL树的平衡性,它非常适合需要频繁查找操作且对性能要求较高的场景。常见的应用场景包括:
- 数据库索引:AVL树常用于实现数据库中的索引结构,能够在大规模数据中提供快速的查找和插入。
- 内存管理:在内存管理系统中,AVL树可以用来维护空闲内存块的列表,从而实现高效的内存分配和回收操作。
- 字典与映射:AVL树可以用来实现字典和映射,支持键值对的快速查找、插入和删除操作。
总之,AVL树通过严格的自平衡机制,保证了树的高度始终保持在O(log n),从而在大多数查找密集型的应用中,能够提供优异的性能表现。
以上就是AVL树的全部啦!希望对你有所帮助,下面是我用K,V模板完成的完整的AVL树代码,希望对你有些帮助!
#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#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; //balance factor
AVLTreeNode(const pair<K, V>& _kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(_kv)
, _bf(0)
{}
};
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->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;//每次插入都和父亲链接上
//现在每次插入要更新平衡因子
while (parent)
{
if (cur == parent->_left) {
parent->_bf--;
}
else {
parent->_bf++;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) {
//继续向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) {
//当前子树出问题了
if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
break;
}
else
{
assert(false);
}
}
return true;
}
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;
}
void RotateR(Node* parent)
{
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;
}
parent->_bf = subL->_bf = 0;
}
void RotateL(Node* parent)
{
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;
}
parent->_bf = subR->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
//单旋结束后平衡因子会记为0,实际上平衡因子会改变
if (bf == -1)//代表在左边插入
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else {
assert(false);
//理论上不可能,万一写错了呢
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (bf == -1)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subRL->_bf = 0;
subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 0;
}
else
{
assert(false);
}
}
void InOrder() {
_InOrder(_root);
}
bool IsBalance()
{
return _IsBalance(_root);
}
private:
void _InOrder(Node* root)
{
if (root == nullptr) {
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " " << root->_kv.second << endl;
_InOrder(root->_right);
}
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)
return false;
return _IsBalance(root->_left) && _IsBalance(root->_right);
}
private:
Node* _root = nullptr;
};