一、AVL树的概念
前面我们学习了二叉搜索树,其在一般情况下,对数据的查找的效率为O(logN),但是在极端的情况下,其时间复杂度会达到O(n)。
如下:

右边的单支或者接近单支的情况就会导致查找的效率变得很低。
所以后来又发明了AVL树,AVL树是两个前苏联科学家在1962年发表的。
其概念如下:
AVL树最先发明自平衡二叉查找树,其是一棵空树,或者具有以下性质的二叉树,它的左右子树也都是AVL树,然后左右子树的高度差不能超过1。AVL树是一棵高度平衡二叉树,其通过控制高度差去控制平衡。
如下所示:

我们此处AVL树的实现中,引用了平衡因子,其每个结点都有平衡因子,这个平衡因子是用来记录该结点的左右子树的高度差的,平衡因子的可能情况为0/1/-1。要是出现了绝对值大于1的情况,那么就说明我们当前的树不符合AVL树的要求,那么我们就要对其进行调整了,这个平衡因子不是必须要的,但是很方便我们对当前树的结构进行判断。
平衡因子=右子树高度-左子树高度
那么为啥不直接将其设计成0的平衡因子呢?
这是因为二叉树的左右子树之间高度差没办法一直保持在0的情况,比如说我们两个结点的情况,此时高度差肯定是1了。
下面是AVL树的特点:
1、高度平衡的二叉搜索树:其结点的平衡因子的绝对值保证是0或者1,避免了出现单支的情况。
2、旋转机制控制平衡:当出现平衡因子绝对值超过1的情况会通过旋转机制来保持其结构是AVL树
3、查找效率稳定:其结构稳定,不会出现极端的情况,效率为O(logn)
二、AVL树的实现
1、AVL树的结构
首先就是二叉树的标配,指向左右子结点的左右指针。然后就是要存储的数据,然后还有平衡因子,我们使用一个int类型数据,然后还有指向父结点的指针。

然后我们再设计一个头结点类,然后将这个结点结构封装到头结点中。

2、insert结点的插入
(1)、新增结点
我们整个数据插入的逻辑就是插入结点,然后更新结点的平衡因子,然后判断这个插入是否有影响整个AVL树的结构,要是影响就做调整。
首先对于空树的情况,那么我们直接插入即可。

然后数据的插入逻辑和前面学习的二叉搜索树是一样的,将插入的结点的数据从根结点开始比较,要是插入的数据大于根结点的数据,那么我们就往右边子树继续找合适的插入位置。

上面的代码,就是将我们的结点先插入到树结构中。
(2)、平衡因子的更新
插入后,我们就要更新平衡因子了,那么我们插入一个结点会对那些结点产生影响呢?
新增加结点后,其只会影响祖先结点的平衡因子,而且可能只是部分祖先结点的平衡因子会被影响,所以我们上面对于结点的设计中会多设计一个指向父节点的指针,在插入结点后对于平衡因子的更新中要经常使用到。平衡因子的更新最坏的情况就是更新到根结点的情况。然后对于啥时候更新可以停止我们下面会进行详细讲解。
平衡因子的更新原则:
平衡因子=右字数高度-左子树高度
只有子树的高度变化才会影响当前结点的平衡因子变化
然后结点的插入导致祖先结点的平衡因子要进行更新几种情况如下:
1、只会影响插入结点的父结点的平衡因子的插入:

例如这种情况,那么我们的新增结点2其是插在1这个结点是右边的,那么按照我们计算平衡因子的方式,那么我们的1结点的平衡因子就要+1。然后要是我们插入的数据是0,那么其应该插入在1这个结点的左边,那么此时1的平衡因子就-1操作。
然后对于平衡因子从0变化,这种情况下都不会破坏当前树的平衡。所以这种情况下,我们只需要将平衡因子都更新完,那么我们的插入工作就完成了。
2、会影响多个祖先结点的插入:

我们看到上面这种插入,其不止影响了其父结点1的平衡因子,然后还使得其父结点的父结点,也就是其爷爷结点的平衡因子也发生了变化。
我们发现当我们父结点的平衡因子变成0的时候,那么我们就不再需要再往上去更新再上一层父结点的平衡因子了,这是因为当其平衡因子变成0的时候,那么就说明当前子树是完全平衡的。
所以对于平衡因子的更新,当我们遇到更新为0的时候就可以停止了。
然后要是更新完是2或者-2的话,那么说明我们当前的树结构被破坏了,要使用旋转方法调整才行。

(3)、旋转调整
旋转在结点的插入中是在当AVL树的结构被破坏的时候使用的。其目的是保持当前树的结构是AVL树,就树的左右高度差保持在1以内。子树之间也是如此。
旋转主要分为四种:左单旋/右单旋/左右双旋/右左双旋。
下面我们对这几种旋转进行详细讲解:
1、右单旋
左单旋,其是应对单单左边子树不平衡的情况,那么树高的部分就要将其和右边子树进行调整
那么其是如何进行调整的呢?
下面我们看几个示例图:

首先我们来看看最简单的,就是h此时为0或者1的时候:

上面是h为0的时候,那么我们发现,经过我们上面的调整后,我们的树的结构还是保持着AVL树的结构。
下面我们看看h为1的情况:

下面是h为3的情况:

我们会发现随着高度的变化,我们新插入的结点的情况就越多,变化特别大。
所以我们将其总结成下面这种情况:

如图所示,我们的a子树,其高度为h,此时a处插入一个结点,导致a子树的高度变为h+1,然后平衡因子更新,导致根结点的平衡因子更新为-2,那么此时就需要进行旋转调整树的结构了。
那么我们看上图,其调整逻辑是将a的父结点的父亲结点变成a的父结点的右结点,然后原来a的父结点的右子树变成原来a的父结点的右孩子的左孩子。要注意的是b可能是空的,然后要是parent是根结点的情况,那么我们也要特殊处理胰一下,避免空指针的访问。
下面是右旋的代码:

这一部分我们通过代码,然后将上面的图按照这一段代码的逻辑进行移动,我们就懂了右旋的逻辑。
然后我们啥情况下使用右单旋呢?
我们观察图中可以看出,当cur的平衡因子是-1的时候,然后cur->parent的平衡因子是-2的时候就要使用我们的右单旋。

2、左单旋
左单旋的话和我们的右单旋的情况有点类似。
其情况如下:
插入一个新结点后,会导致右边不平衡,然后我们要将其向左边进行调整
然后我们的调整方法如下:

如上所示,我们将sudR的左子树变成其父结点的右孩子,然后其父结点变成其左孩子。
然后要注意的是,我们的sudRL可能是空的,所以要加一个条件判断。
然后使用其的情况是,当sudR的平衡因子为1,parent的平衡因子是2的时候,那么此时就使用我们的左单旋。
代码如下:

3、左右双旋
这种情况的话,就是插入到我们的b的位置,然后导致我们的树是中间部分的树更高。
那么我们就没办法调用左右单旋了。

如上,要是新插入一个结点后,那么此时是中间的高,然后我们再看左边子树,这个子树内部是右边的要高。
然后这种情况又一共分为三种场景:



下面我们对上面三种情进行一步步的拆解看看其是如何进行旋转的:
首先是场景1:

对于上面这个树,对于parent这棵树,是左边要高,但是对于其左子树sudL这个结点,其是右边子树要更高,所以我们对于sudL这个子树要使用的是左单旋,然后对于parent这个结点要使用的是右旋。
所以其旋转步骤变化图如下:

首先是对于sudL这棵子树的左旋。然后使得整个树的结构变成如下的情况:

我们看到我们此时的树对于parent这个结点来说就是左边高右边低的了,然后其内部的子树也是如此,那么其就回到了我们前面的单旋的时候的结构了,那么我们再对parent这个结点使用一次右单旋转即可。
旋转完成如下:

然后我们的场景二也是类似:

场景三:

然后对于三种场景的平衡因子的更新都有所不同:
在场景一的情况下,首先新增的结点插入在e这个位置,那么导致其祖先结点8->5->10的平衡因子不断更新,到10这个结点的平衡因子的绝对值超过了1,导致旋转。其中sudLR这个结点的平衡因子在旋转前为-1,然后旋转后sudLR和sudL的平衡因子都为0,parent的平衡因子变为1。
在场景二的情况下,新增的结点是插入在f这个子树的,然后旋转前sudLR的平衡因子为1,旋转后sudRL和parent的平衡因子都变为0,sudL的平衡因子为-1。
场景三的情况下a/b/c子树都是空树,然后是b自己进行新增一个结点,然后对sudL和parent的平衡因子进行更新,旋转前sudLR的平衡因子为0,旋转后sudL、sudLR、parent的平衡因子均为0。
然后其代码实现我们会进行前面的左单旋和右单旋进行复用。
其触发的条件为parent和sudL的平衡因子在插入结点后,parent更新为-2,然后sudL的平衡因子更新为-1。
代码如下:

4、右左双旋
右左双旋和我们上面的左右双旋转的逻辑大差不差。
其使用的情况如下:

其是插入到右子树的左边子树。然后我们右边子树内是左边的子树要更高一点,所以我们要先调整这个子树,使用右单旋,然后再对整个树使用左单旋。
然后就是平衡因子的更新了。
我们的右左单旋也是有三种场景:
场景一:

场景二:

场景三:

然后对于这三种的平衡因子的更新也是不一样的:
对于场景一,其新增结点是插入在e树的,其插入影响了sudRL、sudR、parent的平衡因子,在旋转前,sudRL的平衡因子为-1,然后旋转后,parent和sudRL的平衡因子都变成了0,sudR的平衡因子为1。
然后对于场景二,新增结点是插入在f树的,其影响了sudRL、sudR、parent的平衡因子,在旋转前sudRL的平衡因子为1,旋转后,sudRL和sudR的平衡因子都变成0,然后parent的平衡因子变成了-1。
对于场景三,a/b/c都是空树,所以b是自己新增的一个结点,然后导致sudR和parent的平衡因子变化,旋转前sudRL的平衡因子为0,旋转后,sudRL、sudR、parent的平衡因子都变成了0。
代码如下:

然后我们注意要使用右左单旋的情况:
当parent的平衡因子为2,然后parent->_right的平衡因子为-1的时候使用。
如下:

3、AVL树的查找find
AVL树的查找逻辑和我们的二叉搜索树是一样的:

不过要注意的是我们的查找逻辑是使用关键码key进行查找。
4、求树高

这个也很简单,和前面学习二叉树的时候求二叉树的树高是一样的。
5、求AVL树的结点个数
这个就直接进行一个递归调用就可以:
6、AVL树的平衡检测
这个检测的核心目标是防止一个结点的左右树高超过了1,那么这棵树就违反了AVL树的要求,那么我们是否可以使用平衡因子进行检测呢?
其实不行,我们要是使用平衡因子进行判断,那么我们不就即成了规则的制订者,又成了裁判了。
我们的判断代码如下:

通过求各个子树之间的高度差进行判断。
三、AVL树实现完整代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<class K ,class V>
struct AVL_TreeNdoe
{
AVL_TreeNdoe(const pair<K,V>&kv)
:_kv(kv)
,_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_bf(0)
{
}
private:
pair<K, V>_kv;
AVL_TreeNdoe<K, V>* _parent;
AVL_TreeNdoe<K, V>* _left;
AVL_TreeNdoe<K, V>* _right;
int _bf;
};
template<class K,class V>
class AVL_Tree
{
typedef AVL_TreeNdoe<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)
{
parent = cur;
//注意比较的关键码是key
//插入的数据大于根结点,往右边插
if (cur->_kv.first<kv.first)
{
cur = cur->_right;
}
//插入的数据小于根结点,往左子树插
else if(cur->_kv.first>kv.first)
{
cur = cur->_left;
}
//插入的数据树中已存在,插入失败
else
{
return false;
}
}
//此时cur所在的位置就是结点插入的合适的位置
cur = new Node(kv);
cur->_bf = 0;
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
//插入的结点和树进行链接
cur->_parent = parent;
//更新平衡因子_bf
while (parent)
{
if (parent->_left=cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf==0)
{
return true;
}
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)
{
RotateLR(parent);
}
else if(parent->_bf==2&&cur->_bf==-1)
{
RotateRL(parent);
}
}
else
{
//程序前面就有问题了
assert(false);
}
}
}
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 _IsBalanceTree(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int bf = rightHeight - leftHeight;
if (abs(bf) >= 2 || bf != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return _IsBalanceTree(root->_left)
&& _IsBalanceTree(root->_right);
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
private:
void RotateR(Node* parent)
{
Node* subl = parent->_left;
Node* sudlR = subl->_right;
parent->_left = sudlR;
if (sudlR)
{
sudlR->_parent = parent;
}
Node* parentparent = parent->_parent;
sudl->_right = parent;
parent->_parent = sudl;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parentparent->_left == parent)
{
parentparent->_left = subL;
}
else
{
parentparent->_right = subL;
}
subL->_parent = parentparent;
}
parent->_bf = subL->_bf = 0;
}
void RotateL(Node* parent)
{
Node* sudR = parent->_right;
Node* sudRL = sudR->_left;
parent->_right = sudRL;
if (sudRL)
{
sudRL->_parent = parent;
}
Node* parentparent = parent->_parent;
sudR->_left = parent;
parent->_parent = sudR;
if (parent==_root)
{
_root = sudR;
sudR->_parent = nullptr;
}
else
{
if (parentparent->_right==parent)
{
parentparent->_right = sudR;
}
else
{
parentparent->_left = sudR;
}
sudR->_parent = parentparent;
}
sudR->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* sudL = parent->_left;
Node* sudLR = parent->_left->_right;
//将旋转前sudLR的平衡因子记录下来,方便后续对旋转后的平衡因子进行更新
int bf = sudLR->_bf;
//先对左子树进行左单旋
RotateL(parent->_left);
//再对整体进行右单旋
RotateR(parent);
//旋转完成对平衡因子进行更新
if (bf==0)
{
parent->_bf = 0;
sudL->_bf = 0;
sudLR->_bf = 0;
}
else if(bf==-1)
{
sudL->_bf = 0;
sudLR->_bf = 0;
parent->_bf = 1;
}
else if(bf==1)
{
sudL->_bf = -1;
sudLR->_bf = 0;
parent->_bf=0
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* sudR = parent->_right;
Node* sudRL = sudR->_left;
int bf = sudRL->_bf;
//先对右边子树进行右旋
RotateR(parent->_right);
//再对整体进行左旋
RotateL(parent);
//旋转完成,对平衡因子进行更新
if (bf==0)
{
parent->_bf = 0;
sudR->_bf = 0;
sudRL->bf = 0;
}
else if (bf==1)
{
sudR->_bf = 0;
sudRL->_bf = 0;
parent->_bf = -1;
}
else if(bf==-1)
{
sudR->_bf = 1;
sudRL->_bf = 0;;
parent->_bf = 0;
}
}
private:
Node* _root;
};