目录:
- AVL树的概念
- AVL树节点的定义
- 更新平衡因子
- AVL树的旋转
- AVL树的验证
- AVL的整体实现
- AVL树的删除
- AVL树的性能
- 总结
AVL树的概念
AVL树:二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
特性:
1.它的左右子树都是AVL树
2.左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
图中我们可以看到平衡因子等于右子树的高度减左子树的高度,比如节点3的平衡因子就是1-2=-1,所以节点3的平衡因子就是-1。
AVL树节点的定义
template<class k , class v>
struct AVLTreeNode
{
pair<k, v> _kv;
AVLTreeNode<k, v>* _left;
AVLTreeNode<k, v>* _right;
AVLTreeNode<k, v>* _parent;
int _bf;//balance factor
//成员初值列
AVLTreeNode(const pair<k, v>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_bf(0)//上面的平衡因子受到下面影响,所以初始化为0
{}
};
更新平衡因子
先按照二叉搜索树的规则将节点插入到AVL树中, 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树 的平衡性。调整的话就是左边插入一个节点,平衡因子减减;右边插入,平衡因子就要加加,但新节点插入后,平衡因子可能存在三种情况:
- 如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足 AVL树的性质,插入成功。
- 如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被更新成正负1,此时以parent为根的树的高度增加,需要继续向上更新。
- 如果parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理。
AVL树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。
旋转的目的:
一.让这颗子树的高度差不超过1
二.旋转过程在保持它是搜索树
三.更新调整孩子的平衡因子
四.降低这颗子树的高度跟插入前保持一致
根据节点插入位置的不同,AVL树的旋转分为四种:
1.新节点插入较高右子树的右侧—右右:左单旋
通过上面的抽象图我们看到a/b/c是高度为h的AVL树,c插入节点后,导致高度变化为h+1,右边就高了需要往左边压,就会发生左单转。
左单旋的旋转规则是:节点60的左边b调整到节点30的右边,节点30变成节点60的左边,原来节点30是根,现在节点60是根。为什么这样调整呢?这也是根据二叉树的性质来调整的,旋转前,节点30的右子树一定都大于它,节点60的左子树一定都小于节点60,这样调整也合理,旋转完成后,更新节点的平衡因子即可。
代码如下:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode==nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
parent->_bf = subR->_bf = 0;//最后更新平衡因子
}
- 新节点插入较高左子树的左侧—左左:右单旋
通过上面的抽象图我们看到a/b/c是高度为h的AVL树,a插入节点后,导致高度变化为h+1,左边就高了需要往右边压,就会发生右单转。
右单旋的旋转规则是:节点b变成节点60的左边,节点60变成节点30的右边,原来节点60是根,现在节点30是根,为什么这样调整呢?目的二我们要保证这是一颗搜索数,节点b比节点30大比节点60小,节点60又比节点30大,这样调整也合理,不会破坏这棵树,旋转完成后,更新节点的平衡因子即可。
代码如下:
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = _parent;
subL->_right = parent;
parent->_parent = subL;
if (_root==parent)
{
_root = subL;//sybL是根
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
- 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
通过上面的抽象图我们看到a/d为高度h的AVL树,b/c为高度h-1的AVL树,假设节点b是新增的一个节点,单旋的时候我们的AVL树结构像是一条直线,比较好控制平衡,现在这棵树更像是一条折现,这种情况就需要进行双旋,也就是通过两次旋转来调整平衡。
左右旋转的旋转规则是:第一次节点30为轴点进行一个左单旋,节点b变成30的右边,节点30变成节点60的左边,节点60变成子树的根,第二次节点90为轴点进行一个右单旋,节点c变成节点90的左边,节点90变成节点60的右边,节点60变成子树的根,这个根才是我们旋转两次后得到的这颗子树的根。
这种情况复杂一些,思路就是将双旋变成单旋后再旋转,即:先对节点30进行左单旋,然后再对节点90进行右单旋,旋转完成后再考虑平衡因子的更新。双旋的平衡因子更新比较麻烦,又要分三种情况,注意这里是旋转完后平衡因子的更新,旋转完后的图我就不一一画了,我把旋转之前的图一一画出来供大家参考!
第一种:旋转之前,如果是节点b插入,那么节点60的平衡因子就是-1。
第二种:如果是节点c插入那么节点60的平衡因子就是1。
第三种:节点60自己就是新增,它的平衡因子就是0。
代码如下:
void RotateLR(Node* parent)//左右双旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1) // subLR左子树新增
{
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else if (bf == 1) // subLR右子树新增
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0) // subLR自己就是新增
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
- 新节点插入较高右子树的左侧—右左:先右单旋再左单旋
思路和左右双旋差不多,原理都是一样的,结合抽象图,新增节点后,也是分三种情况:
第一种:旋转之前,如果是节点b插入,那么节点60的平衡因子就是-1。
第二种:旋转之前,如果是节点c插入,那么节点60的平衡因子就是1。
第三种:节点60自己就是新增,它的平衡因子就是0。
代码如下:
void RotateRL(Node* parent)//右左双旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
- 验证其为二叉搜索树:
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。
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);
}
- 验证其为平衡树:
每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)节点的平衡因子是否计算正确。
bool IsBalance()
{
return IsBalance(_root);
}
bool IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) <=1
&& IsBalance(root->_left)
&& IsBalance(root->_right);
}
- 验证用例
像这种树型结构我们最好自己动手画AVL树的创建过程并验证代码是否有漏洞。
常规场景1
{16, 3, 7, 11, 9, 26, 18, 14, 15}
特殊场景2
{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}
我这里写了一个随机种子来验证,我们知道size_t最大可能开辟的数组尺寸是2^64,这里随机给100000个数:
AVL的整体实现
这是我AVLTree.h的代码:
#pragma once
#include <assert.h>
#include <time.h>
template<class K, class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
template<class K, class V>
struct 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;
cur->_parent = parent;
}
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)
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void RotateL(Node* parent)//左旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
parent->_bf = subR->_bf = 0;
}
void RotateR(Node* parent)//右旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//if (_root == parent)
if (ppNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
void RotateLR(Node* parent)//左右双旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1) // subLR左子树新增
{
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else if (bf == 1) // subLR右子树新增
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0) // subLR自己就是新增
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)//右左双旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
//中序
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);
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
int lh = Height(root->_left);
int rh = Height(root->_right);
return lh > rh ? lh + 1 : rh + 1;
}
bool IsBalance()
{
return IsBalance(_root);
}
bool IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) <=1
&& IsBalance(root->_left)
&& IsBalance(root->_right);
}
private:
Node* _root = nullptr;
};
//void TestAVLTree()
//{
// //int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
// //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
// int a[] = { 8,4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
// AVLTree<int, int> t;
// for (auto e : a)
// {
// t.Insert(make_pair(e, e));
// }
//
// t.Inorder();
//
// cout << t.IsBalance() << endl;
//}
void TestAVLTree()
{
srand(time(0));
const size_t N = 100000;
AVLTree<int, int> t;
for (size_t i = 0; i < N; ++i)
{
size_t x = rand();
t.Insert(make_pair(x, x));
//cout << t.IsBalance() << endl;
}
//t.Inorder();
cout << t.IsBalance() << endl;
}
运行一下:
bool返回值是1,说明我们的测试用例没有问题!
AVL树的删除
AVL树的插入有了,那么删除呢?有的伙伴可能会问,因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后要对平衡因子更新,最差情况下一直要调整到根节点的位置。具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这
样可以保证查询时高效的时间复杂度,即
l
o
g
2
(
N
)
log_2 (N)
log2(N)。但是如果要对AVL树做一些结构修改的操
作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,
有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数
据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
总结
假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑
- parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR
当subR的平衡因子为1时,执行左单旋。
当subR的平衡因子为-1时,执行右左双旋。 - parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL
当subL的平衡因子为-1是,执行右单旋。
当subL的平衡因子为1时,执行左右双旋。
旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。
这就是AVL树,下面我们将继续学习红黑树,记得三连哦,小佳会更新更多干货!!!