1、AVL树(平衡二叉搜索树)的概念
AVL树本质是一颗二叉搜索树。
但是对于普通的二叉搜索树,如果数据有序或者接近有序的时候,此时二叉搜索树就是一颗单边树了,此时搜索的效率就很低下。
两位俄罗斯的数学家
G.M.Adelson-Velskii
和E.M.Landis
在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
由此:AVL树的概念,就是具有以下性质的一颗二叉搜索树:
1、要么是空树
2、左右子树均为AVL树
3、左右子树的高度差(平衡因子)的绝对值不超过1。---->因此AVL树也叫平衡二叉搜索树
1、1平衡因子(bf)
如上:平衡因子(Balance Factor,简写为bf) = 右子树高度 - 左子树高度。
-1 <= bf <= 1。
这两棵树不是AVL树哈。
1、2区分AVL树
1、3AVL树的作用
因此:一颗AVL树的结点数为N,那么其高度可保持在logN,因此,搜索的时间复杂度可以在O(logN)。
2、AVL树结点的定义
template<class K, class V>
struct AVLTreeNode
{
//拷贝构造
AVLTreeNode(const pair<K, V>& val = pair < K, V)())
:_kv(val)
, _bf(0)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
{}
pair<K, V> _kv;//存储值
int _bf;//平衡因子
AVLTreeNode* _left;//左孩子
AVLTreeNode* _right;//右孩子
AVLTreeNode* _parent;//父结点
};
这里用<K,V>模型,来存储结点值。
3、AVL树的插入
因为AVL树就是一颗特殊的二叉搜索树,所以AVL树插入结点,可以看作:
1、按照二叉搜索树的方式,插入结点
2、调整平衡因子(旋转)
插入结点之前,所有结点的平衡因子有三种情况:-1,0,1。
插入之后,结点的bf会发生变化,也有三种情况:
1、情况一:新插入结点的父结点(_parent)的_bf变为0。(由1/-1变到0)
2、情况二:新插入结点的父结点(_parent)的_bf变为1/-1。(由0变到1/-1)
当新结点的parent的_bf变为0时,我们需要查看parent的祖先结点的_bf的状态,如果|_bf|大于1了,就需要调整(旋转)。
3、情况三:新插入结点的父结点(_parent)的_bf变为2/-2。(由1/-1变到2/-2)
4、AVL树的旋转
4、1新结点插入到较高左子树的左侧---右单旋
一定是高左子树的左侧,才对应右单选
对于图中的右单旋,它的规则如下:
如图:
举例,理解:
由此,我们可以写出右单旋的代码:
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR != nullptr) subLR->_parent = parent;
Node* p_parent = parent->_parent;//记录parent的父结点
subL->_right= parent;
parent->_parent = subL;
if (p_parent == nullptr)//parent的父结点为nullptr,即parent为根结点
{
_root = subL;
subL->_parent = nullptr;
}
else//parent的父结点不为空,即parent为某个结点的子结点
{
subL->_parent = p_parent;
//看parent是p_parent的左孩子还是右孩子
if (p_parent->_left == parent) p_parent->_left = subL;
else p_parent->_right = subL;
}
subL->_bf = parent->_bf = 0;//bf置零
}
注意:传入的这个parent是平衡因子发生错误的结点。
对于这个结点,它可能是根节点,也可能是某个结点的子结点,需要分类讨论。
4、2新结点插入到较高右子树的右侧---左单旋
左单旋的旋转规则如下:
</