为什么要有AVL树
我们都知道二叉搜索树的规则,插入一个节点时,如果比当前节点值大就到右边,反之则到左边。这样使得中序遍历这颗树可以得到一个有序的数组。如果我们要查找这颗树当中的一个值,最大的时间复杂度是多少呢?O(N),发生这种事情的原因呢,如果我们插入一个有序的数据,它就会排成一条链。
如果树的结构为这种结构的话,我们要查找一个数就可能需要遍历这个数的所有节点。为了解决这个问题,两位俄国的数学家:G.M.Adelson-Velskii和E.M.Landis在1962年发明连里一种解决上述问题的方法:当二叉搜索树中插入新的节点后,如果能保证每个节点的左右子树高度差的绝对值不超过1,即可降低书的高度,从而减少平均搜索长度。
1.AVL树
AVL树是具有以下性质的二叉搜索树:
1.它的左右子树都是AVL树
2.左右子树高度之差(简称平衡因子)的绝对值不超过1.
如果一颗二叉搜索树是高度平衡的。那么它就是AVL树。如果它右n个节点,其高度课保持再logn,搜索时间复杂度为logn
2.实现AVL树
2.1AVL树节点的定义
在二叉平衡树的基础上,添加了平衡因子_bf(左右子树高度之差),以及parent指针,用来指向父节点。
#include <iostream>
using namespace std;
template <class K,class V>
struct AVLTreeNode
{
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;
};
template <class K,class V>
class AVLTree
{
typedef AVLTreeNode<K,V> Node;
public:
private:
Node* _root = nullptr;
};
2.2AVL树的插入
AVL树的插入可以说是AVL树最重要的内容,不过因为AVL树是再二叉平衡树的基础上加入了平衡因子,所以最开始的插入操作和二叉平衡树是相同的。在插入后就要更新平衡因子。
cur插入后,parent的平衡因子一定需要调整,在插入之前,parent
的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
- 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
- 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可
此时:parent的平衡因子可能有三种情况:0,正负1, 正负2
- 如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足
AVL树的性质,插入成功- 如果parent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此
时以parent为根的树的高度增加,需要继续向上更新
bool Insert(const pair<K, V> kv)
{
//1.前期操作与二叉树搜索树的插入操作相似
if (!_root)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;//更新父节点
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;//已存在
}
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;//链接父节点
//2.新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树
while (parent)
{
if (parent->_right == cur)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;//更新后bf为0,表示对二叉树的平衡无影响,可以直接退出
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;//向上更新
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
//出现问题了,要进行处理
}
else
{
assert(false);//表示早已出现问题,直接断错
}
}
return true;
}
以上就是AVL树中插入的基础结构,先进行的二叉搜索树中的插入操作,然后在节点插入过后,因为AVL树的平衡性可能会改变,所以我们要开始对树进行处理。下面就是针对当树的平衡性出现问题时,我们应该进行的操作。即旋转
也就是上面未提到的第3点
3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理
2.2.1.AVL树的旋转
如果在一颗原本平衡的AVL树中插入一个新的节点,造成了AVL树的不平衡。此时我们必须要调整树的结构,使之平衡化。根据插入位置的不同,AVL树的旋转可以分未4种:
1.新节点插入较高左子树的左侧——此时parent的平衡因子为-2,cur的平衡因子为-1
用图片表示:
右旋之后:原本parent和cur的平衡因子都变为0。
上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加
了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,
即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
1. 30节点的右孩子可能存在,也可能不存在
2. 60可能是根节点,也可能是子树
如果是根节点,旋转完成后,要更新根节点
如果是子树,可能是某个节点的左子树,也可能是右子树
void RotateR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
Node* ppnode = parent->_parent;//最后链接更新后的"头节点"
parent->_left = SubLR;
SubL->_right = parent;
if (SubLR)
SubLR->_parent = parent;
parent->_parent = SubL;
if (_root == parent)
{
_root = SubL;
SubL->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = SubL;
}
else
{
ppnode->_right = SubL;
}
SubL->_parent = ppnode;
}
//更新bf
parent->_bf = 0;
SubL->_bf = 0;
}
2.新节点插入较高右子树的右侧——parent的平衡因子为2,cur的平衡因子为1:左旋
具体的操作和上面并没有太多的差别,来看看图片的表示吧:
void RotateL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
Node* ppnode = parent->_parent;
parent->_right = SubRL;
SubR->_left = parent;
if (SubRL)
SubRL->_parent = parent;
parent->_parent = SubR;
if (_root == parent)
{
_root = SubR;
SubR->_parent = nullptr;
}
else
{
if (ppnode->_right == parent)
{
ppnode->_right = SubR;
}
else
{
ppnode->_left = SubR;
}
SubR->_parent = ppnode;
}
SubR->_bf = 0;
parent->_bf = 0;
}
3.新节点插入较高子树的右侧——左右:先左旋再右旋
这种情况比前两种的情况就要复杂了。仅仅旋转一次已经满足不了条件了
画的步骤比较多,从网络偷了一张图:
void RotateLR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateL(SubL);
RotateR(parent);
if (bf == -1)
{
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
SubLR->_bf = 0;
SubL->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
4.新节点插入较高右子树的左侧—右左:先右单旋再左单旋
void RotateRL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateR(SubR);
RotateL(parent);
//更新bf
if (bf == 1)
{
SubR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 0;
SubR->_bf = 1;
}
else
{
parent->_bf = 0;
SubR->_bf = 0;
}
}
完成各个旋转函数的实现,让我们再补起前面的代码吧
bool Insert(const pair<K, V> kv)
{
//1.前期操作与二叉树搜索树的插入操作相似
if (!_root)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;//更新父节点
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;//已存在
}
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;//链接父节点
//2.新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树
while (parent)
{
if (parent->_right == cur)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;//更新后bf为0,表示对二叉树的平衡无影响,可以直接退出
}
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)
{
RotateRL(parent);
}
else
{
RotateLR(parent);
}
break;//更新完毕
}
else
{
assert(false);//表示早已出现问题,直接断错
}
}
return true;
}
AVL树的验证
我们写下了这个程序,但是怎么证明我们写对了呢?总不能使用俺寻思之力吧。为此我们还要写一个验证AVL树的函数。
我们都知道,AVL树一定也是二叉搜索树,所以如果中序打印出来不是一个有序的数组那么一定出问题了。验证完二叉搜索树后就是验证其为AVL树。
1.验证其为二叉搜索树
中序遍历为有序序列
写一个中序遍历,看打印结果
int main()
{
//test
int a[] = { 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();
return 0;
}
打印结果:1 2 3 4 5 6 7 14 15 16
没问题!
2。验证其为AVL树
节点的左右高度差的绝对值一定小于2,且左右高度差等于bf。
1.每个节点子树高度差的绝对值不超过1
2.节点的平衡因子计算是否正确
bool isbanlance()
{
return _isbanlance(_root);
}
private:
bool _isbanlance(Node* root)
{
if (!root) return true;
int hleft = _height(root->_left);
int hright = _height(root->_right);
if (abs(hleft - hright) > 1)
{
cout << root->_kv.first<<"不平衡!" << endl;
return false;
}
if (hright - hleft != root->_bf)
{
cout << root->_kv.first<<"平衡因子异常!" << endl;
return false;
}
return _isbanlance(root->_left) && _isbanlance(root->_right);
}
test
int main()
{
//test
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<int, int> t;
for (auto e : a)
{
t.Insert(make_pair(e, e));
cout << e << "->" << t.isbanlance() << endl;
}
t.inorder();
return 0;
}
打印结果:
4->1
2->1
6->1
1->1
3->1
5->1
15->1
7->1
16->1
14->1
1 2 3 4 5 6 7 14 15 16
目前看没有问题,但这也只是小范围的数据,接下来我们试试大数据的。
可以看出随机数的插入也是没有问题的,那么我们的AVL树可以说是没有问题的。
优化验证函数
在查找树的高度的时候就已经,对树进行了遍历,后续却还要再次遍历一遍树结构,造成了时间的浪费,那么我们是不是可以在查找高度的时候直接把高度带回来,后面也不判断了。
bool _IsBalance(Node* root, int& height)
{
if (root == nullptr)
{
height = 0;
return true;
}
int leftHeight = 0, rightHeight = 0;
if (!_IsBalance(root->_left, leftHeight)
|| !_IsBalance(root->_right, rightHeight))
{
return false;
}
if (abs(rightHeight - leftHeight) >= 2)
{
cout << root->_kv.first << "不平衡" << endl;
return false;
}
if (rightHeight - leftHeight != root->_pf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
return true;
}
代码
#include <iostream>
#include <assert.h>
#include <cstdbool>
#include <vector>
#include <cmath>
#include<utility>
using namespace std;
template <class K,class V>
struct AVLTreeNode
{
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;
};
template <class K,class V>
class AVLTree
{
typedef AVLTreeNode<K,V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
//1.前期操作与二叉树搜索树的插入操作相似
if (!_root)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;//更新父节点
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;//已存在
}
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;//链接父节点
//2.新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树
while (parent)
{
if (parent->_right == cur)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;//更新后bf为0,表示对二叉树的平衡无影响,可以直接退出
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//cur = parent;
cur = 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)
{
RotateRL(parent);
}
else
{
RotateLR(parent);
}
break;//更新完毕
}
else
{
assert(false);//表示早已出现问题,直接断错
}
}
return true;
}
void RotateR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
Node* ppnode = parent->_parent;//最后链接更新后的"头节点"
parent->_left = SubLR;
SubL->_right = parent;
if (SubLR)
SubLR->_parent = parent;
parent->_parent = SubL;
if (_root == parent)
{
_root = SubL;
SubL->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = SubL;
}
else
{
ppnode->_right = SubL;
}
SubL->_parent = ppnode;
}
//更新bf
parent->_bf = 0;
SubL->_bf = 0;
}
void RotateL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
Node* ppnode = parent->_parent;
parent->_right = SubRL;
SubR->_left = parent;
if (SubRL)
SubRL->_parent = parent;
parent->_parent = SubR;
if (_root == parent)
{
_root = SubR;
SubR->_parent = nullptr;
}
else
{
if (ppnode->_right == parent)
{
ppnode->_right = SubR;
}
else
{
ppnode->_left = SubR;
}
SubR->_parent = ppnode;
}
SubR->_bf = 0;
parent->_bf = 0;
}
void RotateRL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateR(SubR);
RotateL(parent);
//更新bf
if (bf == 1)
{
SubR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 0;
SubR->_bf = 1;
}
else
{
parent->_bf = 0;
SubR->_bf = 0;
}
}
void RotateLR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateL(SubL);
RotateR(parent);
if (bf == -1)
{
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
SubLR->_bf = 0;
SubL->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
void inorder()//中序遍历
{
_inorder(_root);
cout << endl;
}
int height()
{
return _height(_root);
}
bool isbanlance()
{
return _isbanlance(_root);
}
private:
bool _isbanlance(Node* root)
{
if (!root) return true;
int hleft = _height(root->_left);
int hright = _height(root->_right);
if (abs(hleft - hright) > 1)
{
cout << root->_kv.first<<"不平衡!" << endl;
return false;
}
if (hright - hleft != root->_bf)
{
cout << root->_kv.first<<"平衡因子异常!" << endl;
return false;
}
return _isbanlance(root->_left) && _isbanlance(root->_right);
}
private:
int _height(Node* root)
{
if (!root) return 0;
int hleft = _height(root->_left);
int hright = _height(root->_right);
return max(hleft, hright) + 1;
}
private:
void _inorder(Node* root)
{
if (!root)
{
return;
}
_inorder(root->_left);
cout << root->_kv.first<<' ';
_inorder(root->_right);
}
private:
Node* _root = nullptr;
};
测试代码
//test
const int N = 1000000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand() + i);
//cout << v.back() << endl;
}
size_t begin2 = clock();
AVLTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
}
size_t end2 = clock();
cout << t.isbanlance();
讲的比较乱。写的也比较赶。大概会有错误的地方,前面写的代码,后面我可能改了,应该都修改了,但也可能会遗漏。文章写到比较杂,感谢你的浏览。
完