AVL树
叫作高度平衡二叉树,性质-》
首先是二叉树,具有二叉树的左小右大,之所以叫做高度平衡,他可以根据插入的顺序来自动调节平衡, 使左右子树的高度差绝对值小于1。so 加入了一个平衡因子来判平衡,并旋转等一些操作来到达平衡。
下面为一个数学规律:
时间复杂度:log2^N
结点树为n,完全二叉树的高 度h = log2n
高度h从零开始计
设高度为h的平衡二叉树最少有多少结点:
n = n(h-1) + n(h-2) + 1
类似与斐波那契数列
h = log2n
基本操作
构造结点-》 由概念可以知道相比于二叉树多了平衡因子balance factor--bf
平衡因子:右子树高度减去左子树的高度,平衡数值:-1 0 1
template <class K, class V>
class AVLtreeNode
{
K _key;
V _value;
int _bf; //平衡因子
AVLtreeNode<K, V>* _left;
AVLtreeNode<K, V>* _right;
AVLtreeNode<K, V>* _parent;
AVLtreeNode<K, V>(const K& k, const V& v)
:_key(k)
, _value(v)
, _bf(0)
, _left(NULL)
, _right(NULL)
, _parent(NULL)
{}
};
这里加了_parent指针,为了之后旋转的时候更方便。如果不加,后面要用栈。
插入操作-》 插入类似与BST,但是多了_bf,
所以在日常的插入后,要调节平衡因子,所以我写了IsBalance()函数,用来更新新的_bf
该函数可以判断新增的节点是左还是右来调整_bf,但这里又有问题,当_bf为-2,2时就 破坏了平衡,需要通过旋转来调节平衡,如何旋转在后续讲。需要注意的是如果_bf从-1,1变为0,实际节点高度是不影响的,所以不需要向上调节了。所以代码如下;
bool inster(const K& k, const V& v)
{
//1.空树时直接开辟空间插入
//2.根据key找到,有相等的key替换
//3.没相等的,找到应该插入的位置
//4.根据多种情况插入节点->> 类似BST 插入函数,注意多了par,bf;
//5.从插入的地方判断平衡因子 ->> IsBanlace(Node);
//6.根据平衡因子旋转。
if (!_root)
{
_root = new Node(k, v);
return true;
}
Node* root = _root;
Node* par = _root;
while (root)
{
if (k < root->_key) {
root = root->_left;
par = root;
}
else if (k > root->_key)
{
root = root->_right;
par = root;
}
else {
root->_value = v;
return false;
}
}
if (!root)
{
root = new Node(k, v);
if (k < par->_key)
{
par->_left = root;
root->_parent = par;
IsBalance(root);
}
else
{
par->_right = root;
root->_parent = par;
IsBalance(root);
}
}
while (root->_parent)
{
root = root->_parent;
}
_root = root;
return true;
}
调节平衡-》所以接下来我们来看看,破坏平衡的几种情况,也就是选择的分离,及如何解决。
旋转:
插入者,被破坏者
RR 左单旋:插入者在被破坏者的右子树的右子数上,也就是RR端,将被破坏者的R结点向上提,自己下去左边,RF作为自己的R。
就像这条红线向左折了一下,就叫左单旋,就这么记吧...
RR左单旋的情况-》 A的_bf本来是1,插入new后变为2, 1-2 左单旋,且A B的_bf同号。可以看出需要断开连接的地方有俩个:A-R , AR - ARL代码如下
void RRroatr(Node* &root)
{
assert(root);
Node* cur = root;
Node* par = cur->_parent;
//root要指向旋转后的根
root = cur->_right;
cur->_right = root->_left;
if (root->_left)
root->_left->_parent = cur;
root->_left = cur;
cur->_parent = root;
cur->_bf = root->_bf = 0;
root->_parent = par;
//不影响上级
if (par)
{
if (root->_key < par->_key)
par->_left = root;
else if (root->_key > par->_key)
par->_right = root;
}
}
LL 右单旋:同理得:插入者是被破坏者的LL节点,使用LL单旋,被破坏者为A,A降为B的右,BR作为自己L,向右折叫做右单旋
LL右单旋的情况-》 A _bf -1->-2 右单旋, 且A 与B的_bf同号。断开的地方:A-AL ,AL-ALR 代码如下
void LLroatr(Node* &root)
{
assert(root);
Node* cur = root;
Node* par = cur->_parent;
root = cur->_left;
cur->_left = root->_right;
if(root->_right)
root->_right->_parent = cur;
root->_right = cur;
cur->_parent = root;
root->_bf = cur->_bf = 0;
root->_parent = par;
if (par)
{
if (root->_key < par->_key)
par->_left = root;
else if (root->_key > par->_key)
par->_right = root;
}
}
RL 右左双旋
先右旋,构造出左旋,再左旋,此时CL可以看作为空。
RL右左旋情况-》可以看出A也是从 1 -> 2,但是B为-1,所以A B异号,这种情况为RL旋转。
旋转过程:1.右旋,对A的右子树进行右旋
2.这时候情况和左旋时一样,直接对A左旋。
LR 左右双旋
先左旋,构造出右旋,在右旋
L左右旋情况-》可以看出A也是从 -1 -> -2,但是B为1,所以A B异号,这种情况为LR旋转。
旋转过程:1.左旋,对A的右子树进行右旋
2.这时候情况和右旋时一样,直接对A左旋。
【注意】因为在程序中加入一个节点是,保证加入前这个树为平衡树,所以不考虑 在非平衡树下加入节点的情况,即 RRR端插入,RLL端插入。
程序中需要考虑三角为子树的情况。都为满足以上四种。做题时出现特殊情况,另行判断即可。
AVL树的旋转是最重要的地方,是大佬想出来的方法,很难通俗的讲清楚,但只要自己动手画一下图,就能很好理解。
所以最后的调节平衡函数为
void IsBalance(Node* root)
{
//根据新增节点位置,添加减少par的BF
//1,-1 -》0 高度不变,不向上递增break
//1, -1 继续向上递增
//2,-2 判断旋转种类,旋转结束退出
//递增至根结束
Node* par = root->_parent;
while (par)
{
if (root->_key < par->_key)
{
par->_bf += -1;
}
else if (root->_key > par->_key)
{
par->_bf += 1;
}
if (par->_bf == 0)
break;
if (par->_bf == 2)
{
if (root->_bf == 1)
RRroatr(par);
else if (root->_bf == -1)
RLroatr(par);
}
else (par->_bf == -2)
{
if (root->_bf == 1)
LLroatr(par);
else if (root->_bf == -1)
LRroatr(par);
}
par = par->_parent;
root = root->_parent;
}
}
插入代码初次写完,出现bug及原因
中途中断 1.选择代码没有用引用,影响了循环,双旋转
2.旋转完后_bf没有归0,影响循环
3.没有判断结点是否为空,就取_par. 中断
次序错误 1.插入结束后没有更新_root结点,再次插入就次序出错。
节点的平衡因子从1,-1变为0则深度不变。若深度改变则要判断对祖节点的影响,是否影响平衡。
判断是否是平衡树 : 时间复杂度 n^2
前序改为后序,降低时间复杂度 O(1)
bool _IsBalance(Node* root, int& height)
{
if(root == NULL)
{
height = 0;
return 1;
}
int leftH = 0;
int right = 0;
if(!(_IsB(root->_left, lH)))
return 0;
if(!(_IsB(root->_right, rH)))
return 0;
height = lh > rh ? lh+1:rh+1;
return abs(rh-lh) < 2;
}
返回高度使用了引用,改变一个数的值。
检查平衡因子