1.为什么提出AVL树
学习完搜索二叉树以后,我们应该想到一个问题,如果我们的搜索二叉树的趋向于单链的形式,类似于:
这两种情况随之而来的是时间复杂度太高,当我们在这两种当中搜索,时间复杂度为O(N),所以这样效率大大下降了!
2.二叉平衡树概念和结构
为了解决上述问题,所以提出了一个概念,叫做二叉平衡树。
二叉平衡树,相对于二叉搜索树,引入了一个叫做平衡因子的概念。
平衡因子:平衡因子就是右子树的深度-左子树的深度。
二叉平衡树的规则是:每一个节点的平衡因子的绝对值都要小于2。
所以我们需要给一个平衡因子。
二叉平衡树的结构:
template<typename K,typename V>
struct AVLBinaryTreeNode
{
typedef AVLBinaryTreeNode<K, V> Node;
AVLBinaryTreeNode(const K& key,const V& value)
:_left(NULL)
,_right(NULL)
, _parent(NULL)
,_key(key)
, _bf(0)
, _value(value)
{}
Node* _left;
Node* _right;
Node* _parent;
int _bf; //来保存右子树高度-左子树高度,平衡因子。
K _key;
V _value;
};
3.二叉平衡树的平衡化旋转
对于二叉平衡树来说,最重要的就是它的旋转算法,因为他要保证所有节点的平衡因子保持在0,1,-1,所以当平衡因子为2或者-2的时候,我们需要调整,这个时候就引入了旋转这个概念。
例如:
比如上面的这个例子,我们可以看出来在这我们的3已经不满足二叉平衡树的条件了,所以在这里我们应该进行旋转。
另外在这里需要知道,对于叶子节点,它的平衡因子都是0。
单旋转
首先我们来看单旋转,单旋转分为两种情况:左单旋和右单旋。
示例代码:
//左旋
void _RotateL(Node *parent)
{
assert(parent);
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
parent->_right = SubRL;
if (SubRL)
SubRL->_parent = parent;
Node * ppNode = parent->_parent; //记录根的父亲
SubR->_left = parent;
parent->_parent = SubR;
if (ppNode==NULL) //考虑根节点的情况
{
_root = SubR;
SubR->_parent = NULL;
}
else
{
if (ppNode->_left == parent) //判断旋转以后的根应该链接在根的父亲的那边
{
ppNode->_left = SubR;
}
else
{
ppNode->_right = SubR;
}
SubR->_parent = ppNode;
}
parent->_bf = SubR->_bf = 0; //重置平衡因子
}
//右旋
void _RotateR(Node *parent)
{
Node *SubL = parent->_left;
Node *SubLR = SubL->_right;
parent->_left = SubLR;
if (SubLR)
{
SubLR->_parent = parent;
}
Node* ppNode = parent->_parent; //记录根节点父亲
SubL->_right = parent;
parent->_parent = SubL;
if (ppNode == NULL) //考虑根节点情况
{
_root = SubL;
SubL->_parent = NULL;
}
else
{
if (ppNode->_left == parent) //判断旋转以后的根应该链接在根的父亲的那边
{
ppNode->_left = SubL;
}
else
{
ppNode->_right = SubL;
}
SubL->_parent = ppNode;
}
parent->_bf = SubL->_bf = 0; //重置平衡因子
}
双旋转
平衡二叉树有时的旋转是双旋转,双旋转有一个特点,就是他的parent节点和下一个节点平衡因子需要异号。
左右双旋转
在这,根据sublr的平衡因子不同,分为了3种情况进行插入。接下来,我们对每一种情况进行分析,讲解。
第一种情况:
是sublr的平衡因子为0,这个时候就可以把sublr当作一个要插入的叶子节点来理解,这样subl的平衡因子为1,parentde平衡因子为-2,这样,就构成了双旋转。
第二种情况:
sublr的平衡因子为-1,这个时候就是在sublr的左子树进行插入节点操作,这样,subl的平衡因子变为1,parent的平衡因子变为-2。
第三种情况:
第三种情况所说的就是sublr的平衡因子为1,这个时候插入点是sublr的右树。这样,subl的平衡因子变为1,parent的平衡因子变为-2。
上述的就是进行右左双旋时的三种情况,我们可以画一个表格总结下:
示例代码:
//左右双旋
void _RotateLR(Node *parent)
{
//进行双旋转的时候通过在这里的旋转以后的根节点的bf进行判断
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
//计算旋转以后的根节点的bf
int bf = SubLR->_bf;
_RotateL(SubL);
_RotateR(parent);
//如果bf为0,代表插入点就是这个节点,这个时候所有旋转后的bf皆为0
if (bf == 0)
{
SubL->_bf = parent->_bf = 0;
}
//当bf为1时,这个时候相当于是在给bf的右树插入,所以插入以后subL旋转后的右边高度为h,
else if (bf == 1)
{
SubL->_bf = -1;
parent->_bf = 0;
}
//当bf为-1,这时相当于是给bf的左树进行插入
else
{
SubL->_bf = 0;
parent->_bf = 1;
}
SubLR->_bf = 0;
}
右左双旋转
接下来我们进行另外一种双旋转的分析,右左双旋,有了上面分析的基础,我相信,下面的分析,你一看就能理解,通过单旋转,我们就可以看到,旋转是镜像的。
- subrl的bf==0
- subrl的bf==1