AVL树
VAL树是一种高度自动平衡的二叉搜索树,它具有稳定的二叉树造型,对于普通搜索二叉树而言,如果遇到了有序的数据,它就会变成一遍到,形同链表的造型,对搜索效率的损伤特别大,而VAL树的自动平衡会调整结构,使二叉树达到高度平衡的造型,让搜索效率大大提高
VAL树的特点
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1
VAL树的节点
struct VALtreeNode
{
VALtreeNode* left;
VALtreeNode* right;
VALtreeNode* _parent;
pair<T, K>_kv;
int _bf;
VALtreeNode(const pair<T,K>&kv)
:left(nullptr)
, right(nullptr)
, _parent(nullptr)
,_bf(0)
,_kv(kv)
{
}
};
VAL树的节点相对于普通二叉树多了父亲节点的指针,形成三叉链的结构,另外多了一个int类型的变量_bf当做平衡因子,用来判断搜索二叉树是否平衡,当该变量的值不是1 -1 0当中的任何一个时,代表二叉树出现平衡失调,需要进行调整,该变量是由右子树的高度减去左子树的高度得到的,在插入新节点的时候,就可以更新该值,如插入右节点的时候,该值加加,插入左节点的时候,该值减减。
AVL树节点的插入
if (_root == nullptr)
{
_root = new Node(kv);;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->right;
}
else
{
return false;
}
}
前面部分正常,寻找新节点的位置,当插入值大于当前节点值,去右子树中找位置,当插入值小于当前节点值,去左子树中寻找位置,并且每次更新节点时,要将该节点的位置保留在parent当中。后序链接新节点,对_parent赋值都需要用到。
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->left = cur;
cur->_parent = parent;
}
else
{
parent->right = cur;
cur->_parent = parent;
}
如果新插入的值比父亲点值小,则链接在父亲节点的左边,如果比父亲节点值大,则链接在父亲节点的右边,同时更新新节点的父亲节点指针。
链接好后,需要更新父亲节点的平衡因子,因为新插入的节点左右子树为空,所以它的平衡因子为零。
while (parent)
{
if (parent->left == cur)
parent->_bf--;
else
parent->_bf++;
int _bf = parent->_bf;
if (_bf == 0)
{
break;
}
else if (_bf == 1 || _bf == -1)//代表可能是父亲的父亲节点出现错误,更新parent和
cur,去上面寻找异常。
{
cur = parent;
parent = parent->_parent;
}
else
{
..........
}
}
当父亲节点的平衡因子为零时,不会对上面的节点产生影响,因为父亲节点所在的子树的高度是没有变化的,不过是父亲节点的左右子树高度达到了平衡,整个包括父亲节点所在的子树的高度并未发生变化。
当父亲节点的平衡因子为1或者-1是,代表父亲节点所在的子树高度产生了变化,会对上面节点的平衡因子产生影响,这时就需要更新parent和cur的值,对上面的平衡因子不断更新,直到到达起始源根停止,如果出现平衡因子不稳定的情况,则需要做出相应调整。
当父亲节点的平衡因子为2或者-2时,代表需要对二叉树的结构进行调整。
else//代表出现异常,需要调整
{
if (parent->_bf == 2 && cur->_bf == 1)//
{
roateL(parent);
break;
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
roateR(parent);
break;
}
else if (parent->_bf = 2 && cur->_bf == -1)
{
roateRL(parent);
break;
}
else if (parent->_bf = -2 && cur->_bf == 1)
{
roateLR(parent);
break;
}
}
需要调整的情况主要是4种,这四种情况以及对应的调整方法就可以应对成千上万的平衡失调。
第一种 parent->_bf==2
当父亲节点的平衡因子为2时,代表右边出现一边高的情况,即左子树的高度比右子高2
我们采用左单旋的调整方式来纠正,但左单旋会有以下两种情况。
void roateL(Node*parent)//左旋转 1.subRL可能为空 2.paretn不一定为空 3.需要改变父节点 4.父亲节点的改变需要判断。
{
Node* grandfather = parent->_parent;
Node* subR = parent->right;
Node* subRL = subR->left;
parent->right = subRL;
subR->left = parent;
parent->_parent = subR;
if (subRL)
{
subRL->_parent = parent;
}
if (_root == parent)
{
subR->_parent = nullptr;
}
else
{
if (grandfather->left == parent)
{
grandfather->left = subR;
}
else
{
grandfather->right = subR;
}
subR->_parent = parent;
}
parent->_bf = subR->_bf = 0;
}
左单旋:以父节点为旋转中心
1.将subR的左子树赋值给父节点的左孩子指针
2.将父节点赋值给subR的右孩子指针
3.改变关键节点的父子关系
最后形成,以subR为父节点的新子树
注:subR为父亲节点的右子树,subRL为subR的左子树,parent为父节点,其中subRL可能为空,需要加以判断。
左单旋中,h不为零的情况其实是有很多种,也就是a,b,c有多种形态,但归根结底都是右子树的高度比左子树的高度高出2,可以统一用一种方法处理。
第二种 paretn->_bf==-2
当父亲结点的平衡因子为-2时,代表左子树的高度比右子树高2,这时候就需要用右单旋来调整高度。
void roateR(Node* parent)//右旋转
{
Node* grandfather = parent->_parent;
Node* subL = parent->left;
Node* subLR = subL->right;
parent->left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
subL->right = parent;
parent->_parent = subL;
if (_root == parent)
{
subL->_parent = nullptr;
}
else
{
if (grandfather->left == parent)
{
grandfather->left = subL;
}
else
{
grandfather->right = subL;
}
subL->_parent = parent;
}
subL->_bf = parent->_bf = 0;
}
右单旋:以父节点为旋转中心
- 将subL的右子树赋值给父亲节点左子树指针
- 将父亲节点赋值给subL的右子树指针
- 改变关键节点的赋值关系
同左单旋一样,在旋转的时候,需要注意subLR是否存在,有可能为空,必须判断一下,否则会运行报错。
第三种 parent->_bf==2&&cur->_bf==-1
父亲节点的平衡因子为2,代表父亲节点的右子树比左子树高出2,而cur为父亲节点的右子树根,它的平衡因子为-1,代表它的左子树比右子树高一,也就是说,新插入的节点是插入在cur的左子树当中,从而引起父亲节点平衡因子的失衡。
这种失衡情况特殊,解决方法是右左双旋,先以cur为旋转中心,右单旋,使整个树造型变为单纯右边高2,然后再左单旋
void roateRL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
int bf = subR->_bf;
roateR(subR);//先以parent的右子树为中心右旋转
roateL(parent);//在以parent的左子树为中心左旋转,此刻该阶段的树已经变成了左边一遍高的树了
if (bf == 0)
{
subR->_bf = parent->_bf = subRL->_bf = 0;
}
else if(bf == 1)//在subRL的右子树插入
{
subRL->_bf = 0;
parent->_bf = -1;
subR->_bf = 0;
}
else if(bf == -1)//在subRL的左子树插入
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else
{
assert(false);
}
}
双旋内部又会有不同的情况,对应的不同情况有不同的平衡因子调节方式,我们先旋转,在最后来彻底更新平衡因子。
右左双旋是先右单旋,再左单旋,我们可以用直接调用之前的函数,提高代码的复用率,在日常中,能够复用尽量复用。
第四种 parent->_bf==-2&&cur->_bf==1
即父亲节点的平衡因子为-2,代表父亲节点的左子树比右子树高出2,而cur作为左子树的根,它的平衡因子为1,代表它的右子树比左子树高1,也就是说,新插入的节点是插入在cur的右子树当中。
这种情况是用左右双旋来完成调整,即先左单旋,使其造型变为右单旋,再用右单旋收尾。
void roateLR(Node* parent)//左右双旋转
{
Node* subL = parent->left;
Node* subLR = subL->right;
int bf = subL->_bf;
roateL(subL);
roateR(parent);
if (bf == 0)
{
subL->_bf = parent->_bf = subLR->_bf = 0;
}
if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
}