文章目录
前言
路漫漫其修远兮,武将上下而求索;
一、什么是平衡二叉搜索树
搜索二叉树虽然可以满足搜索的需求,但是在极端情况下会退化成“链表”的形式而导致其搜索的效率极其低下(搜索效率从O(logN)退化为 O(N)),而其中一种解决这种情况的方法就是AVL树(平衡二叉搜索树)
AVL树是一棵自平衡二叉搜索树,它满足以下性质:
-
可以为空树;
-
若非空,则必须同时满足:
-
是一棵二叉搜索树(满足BST定义,Binary Search Tree(二叉搜索树));
-
左子树和右子树都是AVL树;
-
左右子树的高度差(平衡因子)的绝对值不超过 1。
-
这一性质通过对每个结点维护高度平衡来保证,使树在插入和删除操作后依然保持 O(log n) 的查找效率。
其中,为了控制平衡,在AVL树当中引入了一个平衡因子(balance factor)的概念,每一个节点均有一个平衡因子,任何节点的平衡因子等于右子树的高度-左子树的高度(也可以是左子树高度-右子树高度),也就是说任何节点的平衡因子等于0/1/-1, AVL树并不是必须要平衡因子,但是有了平衡因子就可以更方便我们去观察和控制此树是否平衡;
注:因为插入节点的个数是不确定的,势必会存在左右子树高度差为1的情况,我们只需要维护其高度差不超过1就行;
二、结构
AVL树节点结构,此处实现key-value ,在二叉搜索树结构的基础上增加一个指向父节点的指针、平衡因子:
//定义节点 --> 在二叉搜索树的基础上增加平衡因子、指向父节点的指针,此处实现存储kv
template<class K, class V>
struct AVLTreeNode
{
std::pair<K,V> _kv;
AVLTreeNode<K,V>* _left;
AVLTreeNode<K,V>* _right;
AVLTreeNode<K,V>* _parent;
int _bf;//平衡因子 = 右子树高度- 左子树高度(此处实现中规定)
//构造函数
AVLTreeNode(const std::pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};
AVL树结构:
//定义AVL树形结构
template<class K,class V>
class AVLTree
{
typedef AVLTreeNode<K,V> Node;
public:
AVLTree():_root(nullptr){}
//拷贝构造函数
AVLTree(Node* root){}
//赋值运算符重载
AVLTree<K,V>& operator=(const AVLTree<K,V>& t){}
//析构函数
~AVLTree(){}
//插入
bool Insert(const std::pair<K,V>& kv){}
//右单旋
void RotateR(Node* parent){}
//左单旋
void RotateL(Node* parent){}
//右左双旋
void RotateRL(Node* parent){}
//左右双旋
void RotateLR(Node* parent){}
//中序遍历
void Inorder(){}
//查找 --> 搜索树的效率--》O(logn)
Node* Find(const K& key){}
//高度和size
int Height(){}
int Size(){}
private:
Node* _root = nullptr;
};
三、实现
拷贝构造函数
需要遍历所有节点(前序遍历),创建节点并处理其链接关系
参考代码如下:
//拷贝构造函数
AVLTree(const AVLTree<K,V>& other)
{
_root = copy(other._root , nullptr);
}
Node* copy(Node* root , Node * parent)
{
//前序遍历
if(root==nullptr) return nullptr;
Node* newnode = new Node(root->_kv);
newnode->_parent = parent;
newnode->_bf = root->_bf;
newnode->_left = copy(root->_left ,newnode);
newnode->_right = copy(root->_right, newnode);
return newnode;
}
赋值运算符重载函数
参考代码如下:
//赋值运算符重载
AVLTree<K,V>& operator=(AVLTree<K,V> other)//现代写法,利用现成的拷贝构造+swap
{
std::swap(_root,other._root);
return *this;
}
析构函数
后序遍历释放节点
参考代码如下:
//析构函数 --》后序遍历递归释放节点
~AVLTree()
{
Destory(_root);
_root = nullptr;
}
void Destory(Node* root)
{
if(root==nullptr) return;
Destory(root->_left);
Destory(root->_right);
delete root;
}
插入
与二叉搜索树一致,需要找到插入值应该在的合适的位置(插入一个值的过程按照二叉搜索树的规则进行插入),但是AVL树需要控制其平衡(根据平衡因子的情况进行旋转处理)
AVL树的插入操作包含向上更新平衡因子与局部旋转调整两个关键步骤,旋转后无需继续向上追溯;
过程:
插入节点后,只会影响其祖先节点的高度,因此只需从插入节点开始,沿着父路径向上更新至根节点的平衡因子。实际中,最坏情况下需更新到根节点,但某些情况可在中途停止(如遇到平衡因子变为0时,子树高度不变,无需继续向上更新)。
-
若更新路径上所有节点的平衡因子均保持在
{-1, 0, 1},则插入完成。 -
若遇到某个节点的平衡因子变为
-2或2,则该子树不平衡,需要进行旋转调整。
平衡因子的更新:右子树高度-左子树高度(本篇博文当中,规定为右子树高度-左子树高度)
只有子树高度变化才会影响当前节点的平衡因子;
左单旋

代码如下:
//左单旋
// parent subR
// subR --》 parent
// subRL subRL
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
subR->_left = parent;
if(subRL) subRL->_parent = parent;
Node* parentParent = parent->_parent;
//判断parent 是不是根节点
parent->_parent = subR;
if(parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
//parent 不是根节点,判断parent 位于其父节点的左边还是右边
if(parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
//更新平衡因子
subR->_bf = parent->_bf = 0;
}
右单旋

代码如下:
//右单旋 --》左子树高(bf 为-2)
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
subL->_right = parent;
//subLR的父亲指向
if(subLR) subLR->_parent = parent;
//处理subL与parent 的父亲指向,需要判断parent 是否为根节点
Node* parentParent = parent->_parent;
parent->_parent = subL;
if(parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
//判断subL 在 parentParent 的左边还是右边
if(parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
//处理平衡因子 --》subL 与 parent的平衡因子为0
subL->_bf = parent->_bf = 0;
}
右左双旋
当parent 的平衡因子为2 && subR的平衡因子为-1 时,需要进行右左双旋
对于parent、subR、subRL 平衡因子的更新,需要对subRL的平衡因子分情况讨论 :
- 0 -> parent、subR、subRL 平衡因子均为0
- 1 -> parent 平衡因子为-1、subR、subRL 平衡因子为0
- -1 -> subR 平衡因子为1, parent、subRL 平衡因子为0
图解如下:

代码如下:
//右左双旋 --》parent的平衡因子为2 && subR 的平衡因子为-1
// parent 右单旋 parent 左单旋 subRL
// subR --> subRL --> parent subR
// subRL subR
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;//根据 subRL原来的平衡因子来更新parent subR subRL的平衡因子
//以subR为旋转点进行右单旋
RotateR(subR);
RotateL(parent);
//更新平衡因子
if(bf==0)
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else if(bf == -1)//左边高
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else if(bf == 1)
{
parent->_bf = -1;
subR ->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
左右双旋
当parent 的平衡因子为-2 && subR的平衡因子为1 时,需要进行左右双旋
对于parent、subL、subLR 平衡因子的更新,需要对subRL的平衡因子分情况讨论 :
- 0 -> parent、subL、subLR 平衡因子均为0
- 1 -> subL平衡因子为-1、parent、subLR 平衡因子为0
- -1 -> parent平衡因子为1,subL、subLR 平衡因子为0

代码如下:
//左右双旋 ->parent的平衡因子为-2 && subL的平衡因子为1
// parent 左单旋 parent 右单旋 subLR
// subL --》 subLR --> subL parent
// subLR subL
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
//先以subL为旋转点进行左单旋,再以parent 为旋转点进行右单旋
RotateL(subL);
RotateR(parent);
if(bf==0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else if(bf==1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if(bf ==-1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
插入部分的代码实现:
//插入
bool Insert(const std::pair<K,V>& kv)
{
//找到合适的位置进行插入,向上更新平衡因子,然后根据平衡因子的值进行调整(当前节点的平衡因子超过2需要进行旋转处理)
if(_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root,*prev = nullptr;
while(cur)
{
if(kv.first < cur->_kv.first)
{
prev = cur;
cur = cur->_left;
}
else if(kv.first > cur->_kv.first)
{
prev = cur;
cur = cur->_right;
}
else
{
//找到了相等的,此处实现不支持冗余版本
return false;
}
}
//找到了合适的位置 - 插入在prev 的左子树,平衡因子--,插入在prev 的右子树,平衡因子++
cur = new Node(kv);
if(kv.first < prev->_kv.first)
{
prev->_left = cur;
}
else
{
prev->_right = cur;
}
cur->_parent = prev;
//更新平衡因子
while(prev)//当prev 为nullptr , 意味着cur 为根节点 --》平衡因子更新到根节点就不用再更新了
{
if(cur == prev->_left)
{
prev->_bf--;
}
else
{
prev->_bf++;
}
//根据平衡因子的值来判断需要做什么对应的处理动作
if(prev->_bf == 0)//插入的节点位于矮的那边,树的高度没有变化;不平衡-》平衡
{
break;
}
else if(prev->_bf ==1 || prev->_bf ==-1)//平衡-》不平衡,可能会影响祖先节点
{
cur = prev;
prev = prev->_parent;
}
else if(prev->_bf == 2 || prev->_bf ==-2)//不平衡-》更不平衡;需要进行旋转处理
{
//进行旋转处理 --> 旋转之后就是平衡的,无需再向上更新平衡因子
if(prev->_bf == -2 && cur->_bf == -1)//右单旋
{
RotateR(prev);
}
else if(prev->_bf == 2 && cur->_bf == 1)//左单旋
{
RotateL(prev);
}
else if(prev->_bf == -2 && cur->_bf==1)//左右双旋
{
RotateLR(prev);
}
else if(prev->_bf ==2 && cur->_bf == -1)//右左双旋
{
RotateRL(prev);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
中序遍历
//中序遍历
void Inorder()
{
_Inorder(_root);
std::cout << std::endl;
}
void _Inorder(Node* root)
{
if(root==nullptr) return;
_Inorder(root->_left);
std::cout << root->_kv.first << " ";
_Inorder(root->_right);
}
查找
//查找 --> 搜索树的效率--》O(logn)
Node* Find(const K& key)
{
Node* cur = _root;
while(cur)
{
if(key < cur->_kv.first)
{
cur = cur->_left;
}
else if(key > cur->_kv.first)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
高度
//高度
int Height()
{
return _Height(_root);
}
int _Height(Node* root)
{
if(root==nullptr) return 0;
int left = _Height(root->_left);
int right = _Height(root->_right);
return std::max(left,right) +1;
}
Size
int Size()
{
//遍历求个数
return _Size(_root);
}
int _Size(Node* root)
{
if(root==nullptr) return 0;
return _Size(root->_left) + _Size(root->_right) + 1;
}
四、测试平衡
平衡的测试思想:递归计算当前根节点的平衡因子,与当前所记录的 _bf 进行比较,当这棵树当中的所有平衡因子与我们递归计算的结果相同,就说明我们所写的AVL代码逻辑没有问题;
测试功能函数:
//对AVL树的平衡进行检测 --》遍历平衡因子,按照属性结构的特点计算与当前节点的平衡因子进行比较
bool IsBanlanceTree()
{
return _IsBalanceTree(_root);
}
bool _IsBalanceTree(Node* root)
{
//空树也是AVL树
if(root==nullptr) return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = rightHeight - leftHeight;//此处规定平衡因子 = 右子树高度-左子树高度
//如果平衡因子与我们计算出来的diff 不相等,或者diff 的绝对值大于1,就说明该树不为平衡二叉搜索树
if(abs(diff)>1)
{
std::cout << "当前树不为平衡二叉搜索树" << std::endl;
return false;
}
if(diff != root->_bf)
{
std::cout << root->_kv.first << "平衡因子异常" << std::endl;
return false;
}
//走到此处如果其子树均为平衡二叉树,说明该树为平衡二叉搜索树
return _IsBalanceTree(root->_left)&&_IsBalanceTree(root->_right);
}
测试代码:
#include"AVLTree.hpp"
void TestAVLTree1()
{
zjx::AVLTree<int,int> t;
// int a[] = {16,3, 7,11, 9,26,18,14,15};
int a[] = {4,2,6,1,3,5,15,7,16,14};
for(auto e: a)
{
t.Insert({e,e});
}
t.Inorder();
std::cout << t.IsBanlanceTree() << std::endl;
std::cout <<"高度: " << t.Height() << std::endl;
std::cout << "size: " << t.Size() << std::endl;
zjx::AVLTree<int,int> ct;
ct = t;
ct.Inorder();
}
int main()
{
TestAVLTree1();
return 0;
}
测试结果:

总结
AVL树是一种自平衡二叉搜索树,通过平衡因子(右子树高度-左子树高度)确保树的高度差不超过1,从而维持O(logn)的查找效率。


879

被折叠的 条评论
为什么被折叠?



