目录
一、AVL树介绍
二、AVL树的树节点定义
三、AVL树的插入
1.插入
2.更新平衡因子
3.AVL树的旋转
3.1左旋
3.2右旋
3.3左右双旋
3.4右左双旋
四、中序遍历
五、判断平衡
六、AVL树的删除
在之前,我们已经学习过搜索二叉树了,但是普通的搜索二叉树有他的局限性,在往树中插入的元素有序或者接近有序情况下搜索效率会是O(n)
如上图,搜索的效率会很低下,因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。这种方法以他们两个的名字命名,叫做AVLTree。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
对于这样一棵树,他的搜索效率就会很高,如果它有n个结点,其高度可保持在 O(log2n),搜索时间复杂度 O(log2n)。
我们选择了(key,value)模型,方便数据的操作。跟之前不一样的地方在于AVL树多了一个父节点parent和一个平衡因子bf,有了父节点和平衡因子,我们在插入的时候才能更好的调节树的形状和高度。当平衡因子为0时,代表当前结点是平衡的,为1时,代表当前结点右树比左树的高度高1,为-1时,代表当前节点左树必右树高度高1,以此类推。
template
struct AVLTreeNode
{
AVLTreeNode* _parent = nullptr;
AVLTreeNode* _left = nullptr;
AVLTreeNode* _right = nullptr;
pair _kv;
int _bf;
AVLTreeNode(const pair& kv)
:_kv(kv)
,_bf(0)
{}
};
AVL树的插入部分还是跟之前二叉搜索树十分类似,唯一的区别就是AVL树多了父节点,找到该插入的位置后,将该节点的指针指向父节点即可。
template
class AVLTree
{
typedef AVLTreeNode Node;
public:
bool Insert(const pair& 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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_parent = parent;
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
//后续再进行平衡因子的调节
}
private:
Node* _root;
};
在上面的代码中,我们仅仅是插入了结点,我们现在还不清楚插入结点后,树会变成什么样子,平衡因子是否还平衡,现在我们要来更新平衡因子,判断树是否处于平衡状态。
这里也解答了为什么我们需要父节点的指针,因为插入后,我们需要往上更新平衡因子。
当前节点在左,就让父亲的平衡因子减去1,在右,就让父亲结点的平衡因子加上1。再判断父亲的平衡因子的大小。
1.父亲的平衡因子==0,代表父亲节点已经平衡了,这种是最简单的,不需要调整,直接退出就好(因为平衡因子变为0,证明当前树的高度没有变,也就不会影响上面的树结点)
2.父亲的平衡因子==1或者-1,代表当前结点的高度发生了变化,需要再往上面进行更新。直到平衡因子变为0或者(2和-2)
3.父亲的平衡因子==2或者-2,代表当前结点左右子树高度差了2,已经不满足AVL树的定义了(左右子树高度差最多为1),需要旋转处理。(在下一小节讲解)
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)
{
//不平衡了,需要旋转
}
}
当前AVL树已经不平衡了,需要通过旋转让他保持平衡
else if(parent->_bf == 2 || parent->_bf == -2)
先来看一下下图这种情况(h高度为>=0的树),插入结点后发现树不再平衡,我们该如何通过旋转来解决呢?
这里可以通过左旋处理(将左边高的地方旋转成低的地方),30的右节点指向60的左节点,让60的左节点指向30,这样一来树的高度就降低了1。(之前是h+1+1+1,现在是h+1+1) 同时,我们还可以发现,当前树的结点左右高度也相同了,60和30结点的平衡因子都变成了0。从不平衡旋转成了平衡。
左旋代码如下,对比上图subR为60,subRLeft为60的左节点。为防止h为空树,因此要当subRLeft不为空时,才能将他的父亲指向父节点(也就是上图中的30)。同时我们也得注意所传递的parent并不一定是当前树的根,因此还得判断。grandParent为父节点的父亲,通过grandParent来知道当前节点是父节点的左边还是右,再链接起来。最后旋转完成了,就将subR和parent的平衡因子_bf置为0。(因为已经平衡了)
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRLeft = subR->_left;
parent->_right = subRLeft;
//防止subRLeft为空
if(subRLeft)
subRLeft->_parent = parent;
Node* grandParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (grandParent->_left == parent)
{
grandParent->_left = subR;
}
else
{
grandParent->_right = subR;
}
subR->_parent = grandParent;
}
subR->_bf = parent->_bf = 0;
}
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
跟左旋同理,下图需要右旋,代码也十分类似,就不多赘述了。
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLRight = subL->_right;
parent->_left = subLRight;
//防止subLRight为空
if (subLRight)
subLRight->_parent = parent;
Node* grandParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (grandParent->_left == parent)
{
grandParent->_left = subL;
}
else
{
grandParent->_right = subL;
}
subL->_parent = grandParent;
}
subL->_bf = parent->_bf = 0;
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
对于下图这种情况,在b或者c处添加节点,这变成了一个折线,不同于之前单旋的直线,如果仅仅是一个旋转,是解决不了问题的,我们可以尝试将他化简成之前我们熟悉的直线,那么仅仅需要旋转一次,就可以变成之前的直线了,那我们再次旋转一下,就可以完成折线的平衡了。
我们首先用30结点进行左旋,完成后再用90结点进行右旋,这相当于把60当成当前树的根节点,30在左,90在右。需要注意的是会有某个结点的平衡因子不为0,我们要根据情况修改,下图在b出添加结点,这个情况90的平衡因子为1。
而如果在c处添加结点,这个情况30的平衡因子为-1。
可以先通过代码复用,先以subL左旋旋,再以parent右旋,同时判断subLRight的平衡因子,如果为0,证明插入的结点就是subLRight,直接返回即可,是1或者-1,就可以知道我们是在subLRight左边插入的还是在右边插入的,根据情况修改即可。
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLRight = subL->_right;
int bf = subLRight->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 0)
{
return;
}
else if (bf == -1)
{
parent->_bf = 1;
}
else if (bf == 1)
{
subL->_bf = -1;
}
else
{
assert(false);
}
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
右左双旋也和左右双旋类似,不多赘述,直接上图上代码。
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRLeft = subR->_left;
int bf = subRLeft->_bf;
RotateR(subR);
RotateL(parent);
if (bf == 0)
{
return;
}
else if(bf == -1)
{
subR->_bf = 1;
}
else if(bf == 1)
{
parent->_bf = -1;
}
else
{
assert(false);
}
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
如此一来,我们可以根据各种情况,去进行旋转和平衡因子的调节,就可以变成平衡搜索树了,最后要注意,发生旋转后需要break推出循环,因为已经平衡了,父节点的平衡因子都变成了0,不需要再往上继续更新平衡因子。
中序遍历是老样子,在之前搜索二叉树已经讲解过了,直接上代码。
public:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " " << root->_kv.second << endl;
_InOrder(root->_right);
}
代码测试一下
#include
using namespace std;
#include"AVLTree.h"
#include
int main()
{
int n = 30;
vector v;
v.reserve(n);
//srand(time(0));
for (int i = 0; i < n; i++)
{
v.push_back(rand());
}
AVLTree t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
}
t.InOrder();
}
没毛病
刚刚的中序遍历只能告诉我们,他是一个搜索二叉树,但是我们并不知道他是否平衡,因此我还可以写代码来验证一下。
这里我们可以通过树的高度来进行验证,只要左树的高度跟右树的高度差小于1,就证明是平衡二叉树。代码如下
public:
bool IsBalance()
{
return _IsBalance(_root);
}
private:
//求二叉树的高度
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHegiht = _Height(root->_left);
int rightHegiht = _Height(root->_right);
return 1 + (leftHegiht > rightHegiht ? leftHegiht : rightHegiht);
}
//判断平衡
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_left);
int rightHegiht = _Height(root->_right);
if (rightHegiht - leftHeight != root->_bf)
{
cout << root->_kv.first <<"结点的平衡因子异常" << endl;
return false;
}
return abs(leftHeight - rightHegiht) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
测试代码
int main()
{
int n = 1000;
vector v;
v.reserve(n);
srand(time(0));
for (int i = 0; i < n; i++)
{
v.push_back(rand());
}
AVLTree t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
cout << "插入:" << e << "->" << t.IsBalance() << endl;
}
t.InOrder();
cout << t.IsBalance() << endl;
}
AVL树的相当复杂,我们了解即可
如下,删除左右子树都为空的结点(叶子结点)
如果删除节点后,往上更新时,有结点的平衡因子变成了2或者-2,就需要开始旋转。如果删除非叶子结点,就要像之前的搜索二叉树一样,找结点来代替,之后再更新平衡因子,再判断是否旋转。删除我们了解即可!
最后附上总代码 AVLTree.h
#pragma once
#include
template
struct AVLTreeNode
{
AVLTreeNode* _parent = nullptr;
AVLTreeNode* _left = nullptr;
AVLTreeNode* _right = nullptr;
pair _kv;
int _bf;
AVLTreeNode(const pair& kv)
:_kv(kv)
,_bf(0)
{}
};
template
class AVLTree
{
typedef AVLTreeNode Node;
public:
bool Insert(const pair& 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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_parent = parent;
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
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)//平衡因子变为2或者-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);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRLeft = subR->_left;
parent->_right = subRLeft;
if(subRLeft)
subRLeft->_parent = parent;
Node* grandParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (grandParent->_left == parent)
{
grandParent->_left = subR;
}
else
{
grandParent->_right = subR;
}
subR->_parent = grandParent;
}
subR->_bf = parent->_bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLRight = subL->_right;
parent->_left = subLRight;
//防止subLRight为空
if (subLRight)
subLRight->_parent = parent;
Node* grandParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (grandParent->_left == parent)
{
grandParent->_left = subL;
}
else
{
grandParent->_right = subL;
}
subL->_parent = grandParent;
}
subL->_bf = parent->_bf = 0;
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRLeft = subR->_left;
int bf = subRLeft->_bf;
RotateR(subR);
RotateL(parent);
if (bf == 0)
{
return;
}
else if(bf == -1)
{
subR->_bf = 1;
}
else if(bf == 1)
{
parent->_bf = -1;
}
else
{
assert(false);
}
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLRight = subL->_right;
int bf = subLRight->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 0)
{
return;
}
else if (bf == -1)
{
parent->_bf = 1;
}
else if (bf == 1)
{
subL->_bf = -1;
}
else
{
assert(false);
}
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
return _IsBalance(_root);
}
private:
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHegiht = _Height(root->_left);
int rightHegiht = _Height(root->_right);
return 1 + (leftHegiht > rightHegiht ? leftHegiht : rightHegiht);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_left);
int rightHegiht = _Height(root->_right);
if (rightHegiht - leftHeight != root->_bf)
{
cout << root->_kv.first <<"结点的平衡因子异常" << endl;
return false;
}
return abs(leftHeight - rightHegiht) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " " << root->_kv.second << endl;
_InOrder(root->_right);
}
Node* _root = nullptr;
};
test.cpp
#include
using namespace std;
#include"AVLTree.h"
#include
int main()
{
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//AVLTree avlt;
//for (auto e : a)
//{
// avlt.Insert(make_pair(e,e));
//}
//avlt.InOrder();
//cout< v;
//v.reserve(n);
srand(time(0));
//for (int i = 0; i < n; i++)
//{
// v.push_back(rand());
//}
//AVLTree t;
//for (auto e : v)
//{
// t.Insert(make_pair(e, e));
//}
//t.InOrder();
int n = 1000;
vector v;
v.reserve(n);
srand(time(0));
for (int i = 0; i < n; i++)
{
v.push_back(rand());
}
AVLTree t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
cout << "插入:" << e << "->" << t.IsBalance() << endl;
}
t.InOrder();
cout << t.IsBalance() << endl;
}
感谢大家观看!!!