目录
一、概念
AVL树和红黑树都可以用来作为树形关联式容器的底层实现。但最终采用的是红黑树,虽然他们在时间复杂度上是一样的,但在某些场景下,红黑树更通用。他们依然是二叉搜索树,只不过实现二叉搜索树的方式不同,而被称做平衡二叉搜索树。
AVL树是由两名数学家(G.M.Adelson-Velskii和E.M.Landis)发明出来的一种解决二叉搜索树缺陷的方法,该树的名称也是这两位科学家的简称而得。
AVL树解决问题概念:当向二叉搜索树中插入新结点后,如果能保证每个节点的左右子树高度之差的绝对值不超过1(若超过,需要进行调整,待后续讲解),即可降低树的高度,从而减少平均搜索长度。
AVL树性质:
- 他的左右子树都是AVL树
- 左右子树高度之差的绝对值不超过1(-1/0/1),该高度之差也就是平衡因子
平衡因子= 右子树高度-左子树高度,也可以左减右,没有明确规定,那我采用的是右减左。
注意:是用高度之差计算出平衡因子,不是用平衡因子计算出平衡因子。其次平衡因子并不是必须的,他只是帮助我们更便捷的控制树
思考:既然了解了AVL树的规则,那为什么其高度差是不超过1,而不是0?
因为节点是一个一个插入的,有些情况无法做到左右子树高度相等,比如,2个节点的树,4个节点的树
更全面的平衡二叉搜索树如图:平衡因子记录高度差
AVL树的高度是平衡的,高度可保持在log n,搜索时间复杂度为O(log N)。
二、AVL树的实现
2.1节点定义
节点中包括左指针,右指针,指向父节点的指针,平衡因子,要存储的数据值。
//节点
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data = T())
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_data(data)
{
}
AVLTreeNode<T>* _left;
AVLTreeNode<T>* _right;
AVLTreeNode<T>* _parent;
T _bf;//平衡因子
T _data;//要存储的值
};
2.2AVL树的基础架构
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
typedef Node* PNode;
public:
AVLTree()
:_Root(nullptr)
{}
private:
PNode _Root;
};
2.3AVL树的插入
AVL树的就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
1.按照二叉搜索树的方式插入新节点
2.调整节点的平衡因子
2.3.1按照二叉搜索树的方式插入新节点
bool Insert(const T& data)
{
if (_Root == nullptr)
{
_Root = new Node(data);
return true;
}
PNode cur = _Root;
PNode parent = nullptr;
//找插入点
while (cur)
{
if (data < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else if (data > cur->_data)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//插入
PNode node = new Node(data);
if (data < parent->_data)
{
parent->_left = node;
}
else
{
parent->_right = node;
}
node->_parent = parent;
return ture;
}
2.3.2调整节点的平衡因子
这里就有点漫长了,首先来了解一点,当我们插入节点时,会影响哪些节点的平衡因子?
会影响祖先的平衡因子,有时候是部分的,有时候是全部。至于为什么是这样,这就要来了解平衡因子的更新原则,如图:
由上图可知根据节点的插入位置,来决定哪些祖先的平衡因子需要改变。
那么现在就要来重点讲一讲旋转问题了,当发生不平衡时,根据节点插入位置的不同,AVL树的旋转分为四种:
2.3.2.1 新节点插入较高右子树的右侧:进行左单旋
对于其插入,可以通过抽象图来做代表,通过具象图来进行更好的分析。
根据图的分析,我们可将他转化为代码,其中要注意的是,当旋转完后,也要更新节点中指向父 节点的指针:
//左单旋
void RotateL(PNode parent)
{
PNode subR = parent->_right;
PNode subRL = subR->_left;
PNode pparent = parent->_parent;
if (parent == _Root)//更新根节点
{
_Root = subR;
}
else
{
//更新parent的父节点指向
if (parent == pparent->_left)
{
subR->_parent = pparent;
pparent->_left = subR;
}
else
{
subR->_parent = pparent;
pparent->_right = subR;
}
}
//parent的右指针指向subRL,subRL的父节点指向parent
parent->_right = subR->_left;
if(subRL)//subR的左节点可能不存在
subRL->_parent = parent;
//subR的左指针指向parent,parent的父节点指向subR
subR->_left = parent;
parent->_parent = subR;
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
2.3.2.2新结点插入较高左子树的左侧:进行右单旋
根据图的分析,我们可将他转化为代码:
//右单旋
void RotateR(PNode parent)
{
PNode subL = parent->_left;
PNode subLR = subL->_right;
PNode pparent = parent->_parent;
if (_Root == parent)
{
_Root = subL;
subL->_parent = nullptr;
}
else
{
//更新parent的父节点指向
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
//parent的左指针指向subLR,subLR的父节点指向parent
parent->_left = subLR;
if(subLR)//subR的右节点可能不存在
subLR->_parent = parent;
//subL的右指针指向parent,parent的父节点指向subL
subL->_right = parent;
parent->_parent = subL;
//更新平衡因子
parent->_bf = subL->_bf = 0;
}
2.3.2.3新节点插入较高左子树的右侧:先左单旋再右单旋
有了上述的抽象图了解,这里就不在进行具象图分析,而是直接在抽象图上分析。
当在右侧插入时,其也分为两种情况,为什么要区分这两种情况?
因为他们影响平衡因子的情况不同,所以要做区分。
将上述情况转换为代码:
//左右单旋
void RotateLR(PNode parent)
{
PNode subL = parent->_left;
PNode subLR = subL->_right;
int bf = subLR->_bf;//记录下来,为了后面更新平衡因子
RotateL(parent->_left);//左单旋
RotateR(parent);//右单旋
因为左单旋和右单旋已经破坏了原本的平衡因子,所以需要手动更新
//那么这里以原本subRL的平衡因子来做衡量
if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)//表示新插入节点
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
}
2.3.2.4新结点插入较高右子树的左侧:先右单旋再左单旋
将上述情况转换为代码:
//右左单旋
void RotateRL(PNode parent)
{
PNode subR = parent->_right;
PNode subRL = subR->_left;
int bf = subRL->_bf;//记录下来,为了后面更新平衡因子
RotateR(parent->_right);//右单旋
RotateL(parent);//左单旋
//因为右单旋和左单旋已经破坏了原本的平衡因子,所以需要手动更新
//那么这里以原本subRL的平衡因子来做衡量
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)//表示新插入节点
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
}
2.3.2.5调节平衡因子(完整版插入)
当插入新结点时,更新祖先结点的平衡因子,并一直往上判断,根据祖先结点的平衡因子判断是否需要进行旋转。
bool Insert(const T& data)
{
if (_Root == nullptr)
{
_Root = new Node(data);
return true;
}
PNode cur = _Root;
PNode parent = nullptr;
//找插入点
while (cur)
{
if (data < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else if (data > cur->_data)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//插入
PNode node = new Node(data);
if (data < parent->_data)
{
parent->_left = node;
}
else
{
parent->_right = node;
}
node->_parent = parent;
cur = node;
while (cur)
{
if (parent == nullptr)
break;
//更新平衡因子
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 (cur == parent->_right)
{
if (cur->_bf == 1)
{
//左单旋
RotateL(parent);
}
else if (cur->_bf == -1)
{
//右左单旋
RotateRL(parent);
}
}
else
{
if (cur->_bf == 1)
{
//左右单旋
RotateLR(parent);
}
else if (cur->_bf == -1)
{
//右单旋
RotateR(parent);
}
}
break;
}
else
{
assert(false);//插入之前就有问题
}
}
return true;
}
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将结点删除,然后更新平衡因子,只不过,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
对于AVL树的删除,在这里就不在模拟实现了,因为有了插入的了解,就足够我们进行对AVL树的学习认识。
当然,如果想要进一步了解,可参考<<算法导论>>或<<数据结果-用面向南对象方法与C++描述>>殷人昆版。
2.4AVL树的性能分析
AVL树是一颗绝对平衡的二叉搜索树,弥补了普通版本的二叉搜索树的缺陷。其要求每个节点的左右子树高度差的绝对值不超过1,这样就可以保证查询时高效的时间复杂度,即log N。但是如果要
对AVL树做一些结构修改的操作,性能非常低下,
比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此 :如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会变),可以考虑AVL树,但一个结构经常修改,就不太适合。
三、AVL树的测试
//AVLTree.h
#include <iostream>
#include <vector>
#include <assert.h>
using namespace std;
namespace bit
{
//节点
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data = T())
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_data(data)
{}
AVLTreeNode<T>* _left;
AVLTreeNode<T>* _right;
AVLTreeNode<T>* _parent;
T _bf;//平衡因子
T _data;
};
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
typedef Node* PNode;
public:
AVLTree()
:_Root(nullptr)
{}
bool Insert(const T& data)
{
if (_Root == nullptr)
{
_Root = new Node(data);
return true;
}
PNode cur = _Root;
PNode parent = nullptr;
//找插入点
while (cur)
{
if (data < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else if (data > cur->_data)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//插入
PNode node = new Node(data);
if (data < parent->_data)
{
parent->_left = node;
}
else
{
parent->_right = node;
}
node->_parent = parent;
cur = node;
while (cur)
{
if (parent == nullptr)
break;
//更新平衡因子
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 (cur == parent->_right)
{
if (cur->_bf == 1)
{
//左单旋
RotateL(parent);
}
else if (cur->_bf == -1)
{
//右左单旋
RotateRL(parent);
}
}
else
{
if (cur->_bf == 1)
{
//左右单旋
RotateLR(parent);
}
else if (cur->_bf == -1)
{
//右单旋
RotateR(parent);
}
}
break;
}
else
{
assert(false);//插入之前就有问题
}
}
return true;
}
//左单旋
void RotateL(PNode parent)
{
PNode subR = parent->_right;
PNode subRL = subR->_left;
PNode pparent = parent->_parent;
if (parent == _Root)//更新根节点
{
_Root = subR;
subR->_parent = nullptr;
}
else
{
//更新parent的父节点指向
if (parent == pparent->_left)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
//parent的右指针指向subRL,subRL的父节点指向parent
parent->_right = subR->_left;
if(subRL)//subR的左节点可能不存在
subRL->_parent = parent;
//subR的左指针指向parent,parent的父节点指向subR
subR->_left = parent;
parent->_parent = subR;
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
//右单旋
void RotateR(PNode parent)
{
PNode subL = parent->_left;
PNode subLR = subL->_right;
PNode pparent = parent->_parent;
if (_Root == parent)
{
_Root = subL;
subL->_parent = nullptr;
}
else
{
//更新parent的父节点指向
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
//parent的左指针指向subLR,subLR的父节点指向parent
parent->_left = subLR;
if(subLR)//subR的右节点可能不存在
subLR->_parent = parent;
//subL的右指针指向parent,parent的父节点指向subL
subL->_right = parent;
parent->_parent = subL;
//更新平衡因子
parent->_bf = subL->_bf = 0;
}
//左右单旋
void RotateLR(PNode parent)
{
PNode subL = parent->_left;
PNode subLR = subL->_right;
int bf = subLR->_bf;//记录下来,为了后面更新平衡因子
RotateL(parent->_left);//左单旋
RotateR(parent);//右单旋
因为左单旋和右单旋已经破坏了原本的平衡因子,所以需要手动更新
//那么这里以原本subRL的平衡因子来做衡量
if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)//表示新插入节点
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
}
//右左单旋
void RotateRL(PNode parent)
{
PNode subR = parent->_right;
PNode subRL = subR->_left;
int bf = subRL->_bf;//记录下来,为了后面更新平衡因子
RotateR(parent->_right);//右单旋
RotateL(parent);//左单旋
//因为右单旋和左单旋已经破坏了原本的平衡因子,所以需要手动更新
//那么这里以原本subRL的平衡因子来做衡量
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)//表示新插入节点
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
}
void InOrder()
{
_InOrder(_Root);
cout << endl;
}
int Height()
{
return _Height(_Root);
}
int _Height(PNode Root)
{
if (Root == nullptr)
return 0;
int _LeftHeight = _Height(Root->_left)+ 1;
int _RightHeight = _Height(Root->_right) + 1;
return max(_LeftHeight, _RightHeight);
}
bool _IsBalance()
{
return _IsBalanceTree(_Root);
}
bool _IsBalanceTree(PNode Root)
{
if (Root == nullptr)
return true;
int _LeftHeight = _Height(Root->_left);
int _RightHeight = _Height(Root->_right);
int sub =_RightHeight - _LeftHeight ;
if (sub != Root->_bf)
{
cout << Root->_data << ":" << "平衡因子异常" << endl;
return false;
}
if (sub == 1 || sub == -1 || sub == 0)
{
return _IsBalanceTree(Root->_left) && _IsBalanceTree(Root->_right);
}
else
return false;
}
PNode Find(size_t data)
{
PNode cur = _Root;
while (cur)
{
if (cur->_data < data)
{
cur = cur->_left;
}
else if (cur->_data > data)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return NULL;
}
size_t Size()
{
return _Size(_Root);
}
size_t _Size(PNode cur)
{
if (cur == nullptr)
return 0;
return _Size(cur->_left) + _Size(cur->_right) + 1;
}
private:
void _InOrder(PNode Root)
{
if (Root == nullptr)
return;
_InOrder(Root->_left);
cout << Root->_data<< endl;
_InOrder(Root->_right);
}
PNode _Root;
};
void AVLTreeTest()
{
//int arr[] = { 16,3,7,11,9,26,18,14,15};
int arr[] = { 4,2,6,1,3,5,15,7,16,14 };
AVLTree<int> avl;
for (auto e : arr)
{
avl.Insert(e);
}
avl.InOrder();
cout <<avl._IsBalance();
}
void TestAVLTree2()
{
const int N = 9;
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> t;
for (auto e : v)
{
t.Insert(e);
cout << "Insert:" << e << "->" << t._IsBalance() << endl;
}
size_t end2 = clock();
cout << "Insert:" << end2 - begin2 << endl;
cout << t._IsBalance() << endl;
cout << "Height:" << t.Height() << endl;
cout << "Size:" << t.Size() << endl;
size_t begin1 = clock();
// 确定在的值
for (auto e : v)
{
t.Find(e);
}
// 随机值
for (size_t i = 0; i < N; i++)
{
t.Find((rand() + i));
}
size_t end1 = clock();
cout << "Find:" << end1 - begin1 << endl;
}
}
//test.cpp
#include "AVLTree.h"
int main()
{
bit::AVLTreeTest();
//bit::TestAVLTree2();
return 0;
}
输出结果:
//test.cpp
#include "AVLTree.h"
int main()
{
//bit::AVLTreeTest();
bit::TestAVLTree2();
return 0;
}
输出结果:
end~