AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。所以也称AVL树为二叉平衡搜搜树。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
例子:

AVL结构的定义
可以像下面这样定义成类,也可以定义成结构体,因为结构体的成员的默认权限是公开的(public),下面代码区的定义有点多余
template<class K,class V>
class AVLtreeNode
{
public:
//默认构造函数
AVLtreeNode(const pair<K,V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_kv(kv)
, _bf(0)
{}
//三叉链
AVLtreeNode<K,V>* _left;
AVLtreeNode<K,V>* _right;
AVLtreeNode<K,V>* _parent;
pair<K,V> _kv;
int _bf; //平衡因子 balance factor
};
AVL树之插入
前言:因为搜索树满足一定的比较规则,下面的例子都以左子树都比其父亲节点小,右子树的节点比其父亲节点大。
AVL树的插入实现前面部分与普通的二叉搜索树没有不同,只是多了更新平衡因子和旋转这俩步。
- 寻找合适的插入位置,插入新节点
- 更新平衡因子(左子树高度减右子树高度)
- 平衡因子异常时,进行旋转
而其中平衡因子的更新至关重要,也可以分为俩种情况
- 新插入的节点的父母节点的平衡因子被更新为-1,1时
需要向上继续更新祖父母的平衡因子 - 新插入的节点的父母节点的平衡因子被更新为0时
不需要向上继续更新祖父母的平衡因子,也是更新平衡因子结束的信号
而这样做的原因也很简单,平衡因子被更新为0的节点,先前一定是有一边的子树高一点,而这次插入,正是插入到了较矮的那颗子树里,将高度差补平了,而对其的父母节点乃至祖父母节点的平衡因子毫无影响,因为没有高度的变化;而对于平衡因子被更新为-1,1的节点需要向上更新的原因也正是树的高度发生了变化

插入的完整实现代码:
bool insert(const pair<K, V>& kv)
{
//第一次插入
if (!_root)
{
_root = new Node(kv);
return true;
}
//先寻找插入的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else //找到相等的 说明之前这里有过此元素 所以插入失败了
{
return false;
}
}
cur= new Node(kv);
//开始插入
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right =cur;
cur->_parent = parent;
}
//更新平衡因子和调节平衡 平衡因子等于右子树高度减左子树高度
while (parent)
{
if (cur == parent->_left)
{
--parent->_bf;
}
else if (cur == parent->_right)
{
++parent->_bf;
}
//更新平衡因子
if (parent->_bf == 0)
{
//平衡因子为0,更新结束,因为只是将较矮的新增了个节点,堆父亲没有影响
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) //继续更新
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) //平衡出现错误了 需要进行调整了
{
//判断旋转方式
//1.较高子树为左子树 且插入在较高子树的左子树
if (parent->_bf == -2 && cur->_bf == -1)
{
rotateRight(parent);
}
//2.较高子树为右子树 且插入在较高子树的右子树
else if (parent->_bf == 2 && cur->_bf == 1)
{
rotateLeft(parent);
}
//3..较高子树为左子树 且插入在较高子树的右子树
else if(parent->_bf==-2&& cur->_bf==1)
{
//进行左右单旋
rotateLR(parent);
}
//4.较高子树为右子树 且插入在较高子树的左子树
else if (parent->_bf == 2 && cur->_bf == -1)
{
//进行右左单旋
rotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
//走到这里就是代码逻辑出问题了
assert(false);
}
}
return true;
}
AVL树之旋转
AVL树的旋转分别针对以下几种情况
- 较高子树为右子树,新插入的节点插入到该较高右子树的右子树里:进行左单旋
抽象图:

为了方便操作,我们将平衡因子异常的节点设为parent,异常节点的右节点设为subR,subR的左节点设为subRL
步骤:
- subRL更新为parent的右节点,如果存在的话,且将subRL的父亲节点更新为parent
- 用Parentparent保存parent的父母亲节点
- 将subR的左子节点更新为parent,parent的父母亲节点更新为subR
- 将subR的父母亲节点更新为Parentparent,并分情况讨论:
1.若parent为根节点,即将subL更新为根节点
2.若parent为其他节点的子树,通过Parentparent判断parent所在那颗子树,然后将Parentparent的子树位置更新为subR - 最后将parent和subR的平衡因子更新为0

代码实现:
//左单旋
void rotateLeft(Node* Parent)
{
Node* subR = Parent->_right;
Node* subRL = subR->_left;
//将subLR交给父亲节点的右节点
Parent->_right = subRL;
if (subRL)
subRL->_parent = Parent;
//开始左旋
subR->_left = Parent;
Node* Parentparent = Parent->_parent;
//更新subR的父亲节点
subR->_parent = Parentparent;
Parent->_parent = subR;
//连接上面的节点
if (_root == Parent)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (Parentparent->_left == Parent)
{
Parentparent->_left = subR;
}
else
{
Parentparent->_right = subR;
}
}
//更新平衡因子
Parent->_bf = subR->_bf = 0;
}
- 较高子树为左子树,新插入的节点插入到该较高左子树的左子树里 :进行右单旋
抽象图如下:

为了方便操作,我们将平衡因子异常的节点设为parent,异常节点的左节点设为subL,subL的右节点设为subLR
分别举俩个例子说明步骤:
- subLR更新为parent的左节点,如果存在的话,且将subLR的父亲节点更新为parent
- 用Parentparent保存parent的父母亲节点
- 将subL的右子节点更新为parent,parent的父母亲节点更新为subL
- 将subL的父母亲节点更新为Parentparent,并分情况讨论:
1.若parent为根节点,即将subL更新为根节点
2.若parent为其他节点的子树,通过Parentparent判断parent所在那颗子树,然后将Parentparent的子树位置更新为subL - 最后将parent和subL的平衡因子更新为0

h=2,h=3……的情况也是类似的,就不在此处举例了
代码实现:
void rotateRight(Node * Parent)
{
Node* SubL = Parent->_left;
Node* SubLR = SubL->_right;
//将SubLR交给Parent
Parent->_left = SubLR;
if (SubLR)
{
//SubLR不为空才更新的父亲节点
SubLR->_parent = Parent;
}
//将Parent进行旋转
SubL->_right = Parent;
Node* Parentparent = Parent->_parent;
SubL->_parent = Parentparent;
//更新parent的_parent
Parent->_parent = SubL;
//检查Parent是否为根节点 若为根节点 则更新根节点 连接子树
if (_root == Parent)
{
_root = SubL;
_root->_parent = nullptr;
}
else
{
if (Parentparent->_left == Parent)
{
Parentparent->_left = SubL;
}
else if(Parentparent->_right == Parent)
{
Parentparent->_right = SubL;
}
}
//更新Parent和SubL的平衡因子
Parent->_bf = SubL->_bf = 0;
}
- 较高子树为左子树,新插入的节点插入到该较高左子树的右子树里 :进行左右双旋
抽象图为:

操作步骤:
- 左旋异常节点的左子树
- 再右旋异常节点本身
- 更新平衡因子
而对于双旋来说,只需要复用前面单旋的代码实现即可,但其平衡因子的更新却发生了变化,需要分类讨论

由上图可分为以下三种情况
- 情况一:subLR平衡因子为-1时
subL:0
parent:1
subLR:0 - 情况二:subLR平衡因子为1时
subL:-1
parent:0
subLR: - 情况三:subLR平衡因子为0时
subL:0
parent:0
subLR:0
代码实现:
void rotateLR(Node* Parent)
{
//记录以下节点
Node *subL = Parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
//先左旋其左子树 再右旋其本身
rotateLeft(Parent->_left);
rotateRight(Parent);
if (bf == 0)
{
subLR->_bf = 0;
Parent->_bf = 0;
subL->_bf = 0;
}
else if (bf == 1)
{
subLR->_bf = 0;
Parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
subLR->_bf = 0;
Parent->_bf = 1;
subL->_bf = 0;
}
else
{
assert(false);
}
}
- 较高子树为右子树,新插入的节点插入到该较高右子树的左子树里 进行右左双旋
抽象图解为:

与上面那种情况类似
操作步骤:
- 右旋异常节点的右子树
- 再左
旋异常节点本身 - 更新平衡因子
而对于双旋来说,只需要复用前面单旋的代码实现即可,但其平衡因子的更新却发生了变化,需要分类讨论

由上图可分为以下三种情况
- 情况一:subRL平衡因子为-1时
subR:1
parent:0
subRL:0 - 情况二:subRL平衡因子为1时
subR:0
parent:-1
subRL:0 - 情况三:subRL平衡因子为0时
subR:0
parent:0
subRL:0
代码实现:
void rotateRL(Node* Parent)
{
//记录以下节点
Node* subR = Parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
//先右旋其右子树 再左旋其本身
rotateRight(subR);
rotateLeft(Parent);
if (bf == 0)
{
subRL->_bf = 0;
Parent->_bf = 0;
subR->_bf = 0;
}
else if (bf == 1)
{
subRL->_bf = 0;
Parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == -1)
{
subRL->_bf = 0;
Parent->_bf = 0;
subR->_bf = 1;
}
else
{
assert(false);
}
}
AVL树之查找
同插入原理一般,比较数值,与当前节点进行比较,比此节点大,继续到右子树寻找,比此节点小,继续到左子树寻找
bool find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first >key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < key)
{
parent = cur;
cur = cur->_right;
}
else //找到相等的
{
return true;
}
}
return false;
}
AVL树之验证
AVL树的验证主要分为俩部分
1.是否为平衡二叉搜索树
2.平衡因子是否有错
代码实现
bool IsBalance()
{
return _IsBalance(_root);
}
int Height(Node* root)
{
if (root == NULL)
return 0;
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalance(Node* root)
{
if (root == NULL)
return true;
// 对当前树进行检查
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "现在是:" << root->_bf << endl;
cout << root->_kv.first << "应该是:" << rightHeight - leftHeight << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
AVL性能分析
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
总代码:
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
//namespace AVL
//{
template<class K,class V>
class AVLtreeNode
{
public:
//默认构造函数
AVLtreeNode(const pair<K,V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_kv(kv)
, _bf(0)
{}
AVLtreeNode<K,V>* _left;
AVLtreeNode<K,V>* _right;
AVLtreeNode<K,V>* _parent;
pair<K,V> _kv;
int _bf; //平衡因子 balance factor
};
template<class K,class V>
class AVLTree
{
typedef AVLtreeNode<K, V> Node;
public:
AVLTree()
:_root(nullptr)
{}
void Inorder()
{
_Inorder(_root);
}
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
//中序遍历
_Inorder(root->_left);
cout << root->_kv.first << ' ' <<root->_kv.second<< endl;
_Inorder(root->_right);
}
bool insert(const pair<K, V>& kv)
{
//第一次插入
if (!_root)
{
_root = new Node(kv);
return true;
}
//先寻找插入的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else //找到相等的 说明之前这里有过此元素 所以插入失败了
{
return false;
}
}
cur= new Node(kv);
//开始插入
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right =cur;
cur->_parent = parent;
}
//更新平衡因子和调节平衡 平衡因子等于右子树高度减左子树高度
while (parent)
{
if (cur == parent->_left)
{
--parent->_bf;
}
else if (cur == parent->_right)
{
++parent->_bf;
}
//更新平衡因子
if (parent->_bf == 0)
{
//平衡因子为0,更新结束,因为只是将较矮的新增了个节点,堆父亲没有影响
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) //继续更新
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) //平衡出现错误了 需要进行调整了
{
//判断旋转方式
//1.较高子树为左子树 且插入在较高子树的左子树
if (parent->_bf == -2 && cur->_bf == -1)
{
rotateRight(parent);
}
//2.较高子树为右子树 且插入在较高子树的右子树
else if (parent->_bf == 2 && cur->_bf == 1)
{
rotateLeft(parent);
}
//3..较高子树为左子树 且插入在较高子树的右子树
else if(parent->_bf==-2&& cur->_bf==1)
{
//进行左右单旋
rotateLR(parent);
}
//4.较高子树为右子树 且插入在较高子树的左子树
else if (parent->_bf == 2 && cur->_bf == -1)
{
//进行右左单旋
rotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
//走到这里就是代码逻辑出问题了
assert(false);
}
}
return true;
}
void rotateRight(Node * Parent)
{
Node* SubL = Parent->_left;
Node* SubLR = SubL->_right;
//将SubLR交给Parent
Parent->_left = SubLR;
if (SubLR)
{
//SubLR不为空才更新的父亲节点
SubLR->_parent = Parent;
}
//将Parent进行旋转
SubL->_right = Parent;
Node* Parentparent = Parent->_parent;
SubL->_parent = Parentparent;
//更新parent的_parent
Parent->_parent = SubL;
//检查Parent是否为根节点 若为根节点 则更新根节点 连接子树
if (_root == Parent)
{
_root = SubL;
_root->_parent = nullptr;
}
else
{
if (Parentparent->_left == Parent)
{
Parentparent->_left = SubL;
}
else if(Parentparent->_right == Parent)
{
Parentparent->_right = SubL;
}
}
//更新Parent和SubL的平衡因子
Parent->_bf = SubL->_bf = 0;
}
//左单旋
void rotateLeft(Node* Parent)
{
Node* subR = Parent->_right;
Node* subRL = subR->_left;
//将subLR交给父亲节点的右节点
Parent->_right = subRL;
if (subRL)
subRL->_parent = Parent;
//开始左旋
subR->_left = Parent;
Node* Parentparent = Parent->_parent;
//更新subR的父亲节点
subR->_parent = Parentparent;
Parent->_parent = subR;
//连接上面的节点
if (_root == Parent)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (Parentparent->_left == Parent)
{
Parentparent->_left = subR;
}
else
{
Parentparent->_right = subR;
}
}
//更新平衡因子
Parent->_bf = subR->_bf = 0;
}
void rotateLR(Node* Parent)
{
//记录以下节点
Node *subL = Parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
//先左旋其左子树 再右旋其本身
rotateLeft(Parent->_left);
rotateRight(Parent);
if (bf == 0)
{
subLR->_bf = 0;
Parent->_bf = 0;
subL->_bf = 0;
}
else if (bf == 1)
{
subLR->_bf = 0;
Parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
subLR->_bf = 0;
Parent->_bf = 1;
subL->_bf = 0;
}
else
{
assert(false);
}
}
void rotateRL(Node* Parent)
{
//记录以下节点
Node* subR = Parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
//先右旋其右子树 再左旋其本身
rotateRight(subR);
rotateLeft(Parent);
if (bf == 0)
{
subRL->_bf = 0;
Parent->_bf = 0;
subR->_bf = 0;
}
else if (bf == 1)
{
subRL->_bf = 0;
Parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == -1)
{
subRL->_bf = 0;
Parent->_bf = 0;
subR->_bf = -1;
}
else
{
assert(false);
}
}
bool IsBalance()
{
return _IsBalance(_root);
}
int Height(Node* root)
{
if (root == NULL)
return 0;
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalance(Node* root)
{
if (root == NULL)
return true;
// 对当前树进行检查
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "现在是:" << root->_bf << endl;
cout << root->_kv.first << "应该是:" << rightHeight - leftHeight << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
private:
Node* _root;
};
//}
😗 😗 文章到此就结束了,如果有什么错误的地方,希望各位观看的大佬能指出来,相互学习,相互进步,以及AVL树的删除因为面试不常考,可能以后会更新。

AVL树是一种自平衡的二叉搜索树,通过插入操作后的旋转保持左右子树高度差不超过1,确保高效的查找性能。文章详细介绍了AVL树的插入过程,包括平衡因子的更新和四种旋转操作:左旋、右旋、左右旋和右左旋,以及如何验证AVL树的平衡性。
3971

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



