目录
一、相关概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,插入有序数据时,查找元素相当于在顺序表中搜索元素,效率低下。于是建立了新的结构,当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树的底层仍然是搜索二叉树,不过是一颗高度平衡的二叉树;
搜索二叉树的单只情况查找效率低,近似为O(N)查找效率
二、AVL树节点构造
Q:如何来控制搜索二叉树的平衡问题,是AVL的关键,我们向每个节点结构添加平衡因子 ,何为平衡因子呢?
A:平衡因子=右子树高度-左子树高度
平衡因子并不是AVL所必须具有的,它只是我们来模拟AVL所借助的一种控制方式
其次为了向上查找,还增加了parent的节点,能够找到当前节点的父亲节点,让节点之间更加灵活,便于我们做相关操作。下图为,模拟AVL树(kv模型)的节点结构
三、AVL树的插入情况分析
1. 按照二叉搜索树的方式插入新节点
(1)如果树中有此元素,插入失败
(2)若没有此元素,则找到应该插入的正确位置
注意
循环找位置过程中,保留父亲节点,方便新节点与树的链接(即新节点与父亲节点的链接)
2. 调整节点的平衡因子
(1)更新原则
新增节点是父亲的左边,父亲的平衡因子加一,
新增节点是父亲的右边,父亲的平衡因子减一。
(2)新增节点对部分祖先节点的影响
是否更新取决于parent的高度是否发生变化,是否影响到了爷爷节点,这个更新有可能持续到根节点,所以我们借助条件(parent不为空)来判断循环结束。
1、更新后parent的平衡因子为0,说明parent所在的子树高度不变,不会影响到爷爷节点的平衡因子
(更新前,parent的平衡因子为1或者-1,更新后左右均衡了,parent的高度不变,不会影响到爷爷节点)
2、更新后parent的平衡因子为1或者-1,parent所在的子树高度变了,会影响到爷爷节点,
(更新前parent的_bf是0,parent的一边插入后不均衡了),继续向上更新爷爷节点
3、更新后parent的_bf为2或者-2,parent违反了平衡规则,需要进行旋转处理
旋转之后,高度回到插入之前,不会影响到其他祖先,直接退出调整。
四、旋转处理
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化,降低当前树的高度。根据节点插入位置的不同,AVL树的旋转分为四种,覆盖所有场景:
不平衡是插入某一个节点所导致的,导致根节点的平衡因子绝对值为2,或者-2,如果一棵树本来是平衡的,那么导致一树根不平衡,那么就只有四个节点位置,不同位置对应不同的解决办法:
1、根的左子树的左子树导致的不平衡(右单旋)
2、根的左子树的右子树导致的不平衡(左右旋)
3、根的右子树的左子树导致的不平衡(左单旋)
4、根的右子树的右子树导致的不平衡(右左旋)
可以理解为1,4情况是挂在子树的边缘。2,3情况是挂在子树的中间
平衡因子图形表示:
树中插入节点,如果某一棵子树平衡因子异常,在这颗子树中出现的问题都只会是上图四种情况之一。
介绍四种情况的对应解决办法之前,首先我们先记住几个标量
parent:平衡因子异常的节点
subR :parent节点的右孩子
subRL :parent节点的右孩子的左孩子
subL :parent节点的左孩子
subLR :parent节点的左孩子的右孩子
(1)右单旋
1、处理思路
将subL的原来右节点subLR变为parent的左节点,将subL的新右节点变为parent节点
2、处理流程
(1)、处理parent与subLR新关系的链接(subLR不为空,其父节点指向parent)
(2)、处理parent与subL新关系的链接(保存旧parent的父亲节点与subL节点链接)
(3)、处理parent的父亲节点与subL节点链接(parent可能是根节点)
3、平衡因子调整
parent的平衡因子置0,被旋转到根位置的子节点的平衡因子置0(subL)
注意细节:
1、subLR可能是空,他没有父节点,不能其父节点指向parent
2、parent可能是根节点,旋转完成后,要更新根节点
3、注意链接旧的根位置节点的parent和根位置的新节点,非根节点:parent是parent父亲的左节点,那么parent父亲的左节点与subL节点链接,parent是parent父亲的右节点,那么parent父亲的右节点与subL节点链接
(2)左单旋
旋转思路以及方法同右单旋一样,两个情况是相似的,在图形表示上成对称情况。
(3)先左单旋再右单旋
Q:为什么不可以单旋解决?
A:单旋会将原来一边高的,变到另外一边高。所以这种情况下要进行左右旋。
1、处理方法:
1、先对subL进行左旋,将高度最大的子树全部挂到子树最边缘处
2、再对parent进行右旋
2、平衡因子的控制
我们看图的最终结果可以知道,新节点插在subLR的左节点和右节点是不同的,因为subLR的左节点会分给subL,而右节点会分给parent,这导致,插入在subRL的不同位置,我们最终在调整平衡因子的时候会有不同。甚至在h=0的情况下,subLR就是一个新插节点,调整平衡因子的时候会有不同。所以分为三种情况来讨论平衡因子的调节。如何来判断是哪种情况,就要查看我们插入新节点后subRL的平衡因子来判断了。
(1)b插入(subRL==-1)
parent平衡因子置1
subL平衡因子置0
subLR平衡因子放置0
(2)c插入(subRL==1)
parent平衡因子置0
subL平衡因子置-1
subLR平衡因子放置0
(3)本身是新插节点(subRL==0)
parent平衡因子置0
subL平衡因子置0
subLR平衡因子放置0
(4)先右单旋再左单旋
同理右左旋:原理及处理办法相似,在图形上表示为对称情况,不做相关分析
1、处理方法:
1、先对subR进行右旋,将高度最大的子树全部挂到子树最边缘处
2、再对parent进行左旋
2、平衡因子的控制
(1)b插入(subRL==-1)
parent平衡因子置0
subL平衡因子置1
subLR平衡因子放置0
(2)c插入(subRL==1)
parent平衡因子置-1
subL平衡因子置0
subLR平衡因子放置0
(3)本身是新插节点(subRL==0)
parent平衡因子置0
subL平衡因子置0
subLR平衡因子放置0
总结
1、双旋可以直接调用所需要的单选
2、旋转之后,高度回到插入之前,不会影响到其他祖先,直接退出调整。
旋转之后,高度回到插入之前,不会影响到其他祖先,直接退出
五、AVL树的验证
1、中序遍历
利用递归子函数套一层,传递传递根节点,开始遍历
(不能说明是一颗AVL树,还需要验证平衡)
2、AVL平衡检查
不能利用层序或者节点存储的平衡因子来验证AVL是否平衡,层序无法知道下一层节点所在的具体位置,而存储的平衡因子可能本来就有错误,用错误数值判断,是一种监守自盗;
检查思路:
1、递归,通过平衡因子定义,计算每个节点的实际平衡因子(右高-左高)
2、检查每个节点的存储平衡因子对不对(平衡因子是否异常)
3、检查平衡因子绝对值是不是小于2(平衡因子是否异常)
4、必须判断完所有节点,才能返回true
前序检查
重复计算子树的高度,效率很低
后序检查
1、边判断平衡,边求出高度
2、设置高度应用参数,子树递归后,将子树的高度从深一层的递归当中带出来,即把子树高度带给上一层(核心)
3、把判断写在前面,先走到最深层,倒着走会根节点,逐个判断是否平衡
3、调试技巧
1、每插入一个数据后,用isblance(),看是插入谁导致出现的问题
2、在插入问题数据之前,打条件断点,画出插入前的树
3、单步跟踪,对比图一一分析细节原因
六、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log_2 (N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
随机插入效率很高,查找效率也很高,在release模式下极快
七、AVL模拟代码
#pragma once
template<class K, class V >
struct AVLTreeNode {
typedef AVLTreeNode<K, V> Node;
Node* _parent;
Node* _left;
Node* _right;
int _bf;
pair<K, V> _kv;
AVLTreeNode(const pair<K, V>& kv)
:_parent(nullptr),
_left(nullptr),
_right(nullptr),
_bf(0),
_kv(kv)
{}
};
template<class K, class V >
class 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 (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->_left = cur;
else
parent->_right = cur;
//将新节点与parent链接
cur->_parent = parent;
while (parent)
{
//更新原则
//新增节点是父亲的左边,父亲的平衡因子加一,
//是父亲的右边,父亲的平衡因子减一。
if (parent->_left == cur)
parent->_bf--;
else
parent->_bf++;
//是否更新取决于parent的高度是否发生变化,是否影响到了爷爷节点
//1、更新后parent的_bf为0,说明parent所在的子树高度不变,不会影响到爷爷节点的平衡因子
//(更新前,parent的_bf为1或者-1,更新后左右均衡了,parent的高度不变,不会影响到爷爷节点)
if (parent->_bf == 0)
break;
//2、更新后parent的_bf为1或者-1,parent所在的子树高度变了,会影响到爷爷节点,
//(更新前parent的_bf是0,parent的一边插入后不均衡了),继续向上更新爷爷节点
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = cur->_parent;
}
//3、更新后parent的_bf为2或者-2,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);
}
//旋转后,相比插入之前,树的高度不变,不会影响到祖先节点,直接退出
break;
}
else
{
//插入前就有问题
assert(false);
}
}
return true;
}
//左旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//处理parent与subRL新关系的链接
parent->_right = subRL;
//subRL不为空,其父节点指向parent
if (subRL)
subRL->_parent = parent;
//处理parent与subR新关系的链接
subR->_left = parent;
Node* ppnode = parent->_parent;//保存旧parent的父亲节点与subR节点链接
parent->_parent = subR;
//处理parent的父亲节点与subR节点链接
//parent可能是根节点
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == ppnode->_left)
ppnode->_left = subR;
else
ppnode->_right = subR;
subR->_parent = ppnode;
}
//平衡因子调整
parent->_bf = 0;
subR->_bf = 0;
}
//右旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppnode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
parent->_bf = 0;
subL->_bf = 0;
}
//左右旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//通过subLR的平衡因子确定新插的节点在什么位置
//分情况更新平衡因子
int bf =subLR->_bf;
//先对左节点左旋,在对parent节点右旋
RotateL(subL);
RotateR(parent);
//h=0的情况,subLR本身就是新增节点
if (bf == 0)
{
subLR->_bf = 0;
parent->_bf=0;
subL->_bf = 0;
}
//插在中右
else if (bf == 1)
{
subLR->_bf = 0;
parent->_bf = 0;
subL->_bf = -1;
}
//插在中左
else if (bf == -1)
{
subLR->_bf = 0;
parent->_bf = 1;
subL->_bf = 0;
}
else
{
assert(false);
}
}
//右左旋
void RotateRL(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
//通过subRL的平衡因子确定新插的节点在什么位置
//分情况更新平衡因子
int bf = subRL->_bf;
//先对右节点右旋,然后对parent节点左旋
RotateR(subR);
RotateL(parent);
//更新平衡因子
//h=0的情况,subLR本身就是新增节点
if (bf == 0)
{
subRL->_bf = 0;
parent->_bf = 0;
subR->_bf = 0;
}
//插在中右
else if (bf == 1)
{
subRL->_bf = 0;
parent->_bf = -1;
subR->_bf = 0;
}
//插在中左
else if (bf == -1)
{
subRL->_bf = 0;
parent->_bf = 0;
subR->_bf= 1;
}
else
{
assert(false);
}
}
//中序遍历
void Inorder()
{
_inorder(_root);
cout << "end"<<endl;
}
void _inorder(Node* root)
{
if (root == nullptr)
return;
_inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_inorder(root->_right);
}
//检查是否满足AVL平衡
bool isBalance()
{
//前序递归检查,效率低
//return isblance(_root);
//后序递归检查
int height = 0;
return _isBalance(_root,height);
}
//前序递归检查,效率低
bool isbalance(Node* root)
{
if (root == nullptr)
return true;
int bf = height(root->_right) - height(root->_left);
if (abs(bf) >= 2)
{
cout << root->_kv.first << "不平衡" << endl;
return false;
}
if (bf != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return isbalance(root->_left) && isbalance(root->_right);
}
//改为后序递归检查
bool _isBalance(Node* root, int &height)
{
//增加高度应用参数,将递归得到的子树高度保存,返还给上一级
// 很巧妙的设计
if (root == nullptr)
{
height = 0;
return true;
}
int lefth, righth;
//存在子树不平衡,就返回false
if (!_isBalance(root->_left,lefth) || !_isBalance(root->_right,righth))
{
return false;
}
int bf = righth-lefth;
if (abs(bf) >= 2)
{
cout << root->_kv.first << "不平衡" << endl;
return false;
}
//根据实际算出来的平衡因子,和存储的平衡因子对比
if (bf != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
height = lefth > righth ? lefth + 1 : righth + 1;
return true;
}
//求当前树的高度
int Height()
{
return height(_root);
}
//求高度子函数
int height(Node* root)
{
if (root == nullptr)
return 0;
int lefth = height(root->_left);
int righth = height(root->_right);
return lefth > righth ? lefth + 1 : righth + 1;
}
//树的节点个数
size_t Size()
{
return _Size(_root);
}
size_t _Size(Node* root)
{
if (root == NULL)
return 0;
return _Size(root->_left)
+ _Size(root->_right) + 1;
}
//查找节点
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 NULL;
}
private:
Node* _root = nullptr;
};