目录
AVL树概念
AVL树结构
insert
AVL树的旋转
新节点插入较高右子树的右侧---右右:左单旋
新节点插入较高左子树的左侧---左左:右单旋
新节点插入较高左子树的右侧---左右:先左单旋再右单旋
新节点插入较高右子树的左侧---右左:先右单旋再左单旋
insert实现
测试二叉树是否为AVL树
AVL树概念
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1,超过1需要对树中的结点进行调整。即可降低树的高度,从而减少平均搜索长度。
一棵AVL树性质:
1.它的左右子树都是AVL(任意一个子树左右高度差都不超过1)
2.左右子树高度之差(简称平衡因子)的绝对值不超过1(平衡因子是其中一种实现方式,判断平衡因子即可判断是否为AVL树)
3.平衡因子 = 右子树高度 - 左子树高度
4.AVL树是一棵绝对平衡的二叉搜索树,查询的时间复杂度log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
AVL树结构
引入平衡因子,需要三岔链,原因在于新插入节点还需保持AVL树的特性,新插入节点后平衡因子需要改变(更新新插入节点祖先路径)
template<class K,class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;//平衡因子
pair<K, V> _kv;
};
template<class K,class V>
class AVLTree
{
public:
typedef AVLTreeNode<K, V> Node;
private:
Node* _root;
};
insert
插入后AVL树有可能导致不平衡,此时需要沿着parent更新平衡因子
更新平衡因子规则:
1.如果此时是在parent的右,parent平衡因子++
2.如果此时是在parent的左,parent平衡因子--
3.更新后parent平衡因子如果为0,说明之前是1--/-1++,代表左右子树一边高一边低。插入后变为0,表示两边子树一样高。此时parent则不用继续往上更新(左右子树平衡)
4.更新后parent平衡因子如果为1/-1,说明原来parent平衡因子只可能是0,0代表左右子树高度相等。更新完后为1/-1,代表左右子树一边高一边低,表示parent的高度也改变了,继续往上更新平衡因子(子树高度改变影响父亲)
5.更新后praent平衡因子如果为2/-2,此时parent所在的子树需要旋转处理
insert的基本逻辑,不包括旋转,旋转下面总结
bool insert(const pair<K,V>& kv)
{
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;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
cur->_parent = parent;
//while控制平衡因子,最坏的情况下,更新到根节点
while (parent)
{
if (parent->_left == cur)
{
parent->_bf--;
}
else if (parent->_right == cur)
{
parent->_bf++;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
parent = parent->_parent;
cur = cur->_parent;//沿着三岔链往上走
}
else if (parent->_bf == 0)
{
break;
}
else if(parent->_bf == 2 || parent->_bf == -2)//parent所在子树不平衡,开始旋转
{
}
else
{
assert(false);//直接报错,插入之前平衡因子已经出问题
}
}
return true;
}
AVL树的旋转
旋转规则:
1.如果有不平衡的情况,一定在parent(parent和cur是沿着三岔链更新上去的)
2.旋转成平衡二叉搜索树树
3.旋转后还需要继续保持平衡二叉搜索树的特性
4.具象图有无限种情况,此时我们需要抽象总结归纳avl树旋转的场景和情况
新节点插入较高右子树的右侧---右右:左单旋
抽象图:h可以是任意高度(h>=0)
新节点插入较高右子树的右侧c中(无论h有多高, 新增的节点在c的左or右),插入在c中结果都是parent引发的旋转(parent->bf == 2)
旋转方式:让b作为parent的右子树(b中所有节点都比parent大,没有违反规则);让parent这颗树(a+b)作为tmp处的左子树(所有节点都小于tmp,没有违反规则),此时左子树是h+1,右子树是h+1,整棵树保持高度平衡
此时旋转就有三个位置需要标记:parent位置,parent->right称为subR,parent->right->left称为subRL
此时有以下情况需要处理:
1.注意parent旋转后,有可能是根节点,有可能是整棵树中的一个子树,此时还需要保存parent->_parent,来让旋转后的subR指向_parent
2.如果parent->_parent为nullptr,则代表旋转后subR变成根节点
3.需要改变六个指针的位置
4.一次插入最多更新高度次,在高度次中,只会出现一次旋转更新。如果在while更新平衡因子过程中出现旋转,旋转完成后整棵树即可保持平衡,插入之前树的高度是h+2,插入时高度变成h+3,旋转完成后高度继续变为h+2,对上一层没有影响,直接break跳出旋转即可
5.只有parent和subR的平衡因子受到影响(a,b不变),旋转完后parent和subR平衡因子变为0
void RotaL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* pparent = parent->_parent;
parent->_right = subRL;//1
if (subRL != nullptr)
{
subRL->_parent = parent;//2
}
subR->_left = parent;//3
subR->_parent = pparent;//4
parent->_parent = subR;//5
if (pparent)
{
if (pparent->_left == parent)
{
pparent->_left = subR;//6
}
else
{
pparent->_right = subR;//6
}
}
else//root == nullptr,此时subR为根
{
_root = subR;//6
}
}
新节点插入较高左子树的左侧---左左:右单旋
新节点插入较高左子树的左侧a中(无论h有多高, 新增的节点在a的左or右),插入在a中结果都是parent引发的旋转(parent->bf == -2)
旋转方式:让b作为parent的左子树(b中所有节点都比parent小,没有违反规则);让parent这棵树 (b+c)作为tmp处的右子树(所有节点都大于tmp,没有违反规则),此时左子树是h+1,右子树是h+1,整棵树保持高度平衡
此时旋转就有三个位置需要修改标记:parent位置,parent->left称为subL,parent->left->right称为subLR
此时有以下情况需要处理:
1.注意parent旋转后,有可能是根节点,有可能是整棵树中的一个子树,此时还需要保存parent->_parent,来让旋转后的subR指向_parent
2.如果parent->_parent为nullptr,则代表旋转后subR变成根节点
3.需要改变六个指针的位置
4.一次插入最多更新高度次,在高度次中,只会出现一次旋转更新。如果在while更新平衡因子过程中出现旋转,旋转完成后整棵树即可保持平衡,插入之前树的高度是h+2,插入时高度变成h+3,旋转完成后高度继续变为h+2,对上一层没有影响,直接break跳出旋转即可
5.只有parent和subR的平衡因子受到影响(b,c不变),旋转完后parent和subR平衡因子变为0
void RotaR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* pparent = parent->_parent;
parent->_left = subLR;//1
if (subLR)
subLR->_parent = parent;//2
subL->_right = parent;//3
parent->_parent = subL;//4
subL->_parent = pparent;//5
if (pparent)
{
if (pparent->_left == parent)
{
pparent->_left = subL;//6
}
else
{
pparent->_right = subL;//6
}
}
else
{
_root = subL;//6
}
parent->_bf = subL->_bf = 0;
}
新节点插入较高左子树的右侧---左右:先左单旋再右单旋
如果此时新插入的节点在新节点插入较高左子树的右侧,不能使用右单旋
只使用右单旋解决:无法解决问题(从左子树平衡因子2变成右子树平衡因子2,旋转呈对称)
旋转思路:先对30进行左单旋,再对60进行右单旋
旋转的过程不变,但是平衡因子的更新却不同,原因在于可能插入在b中,也可能插入在c中
void RotaLR(Node* parent)
{
RotaL(parent->_left);
RotaR(parent);//parent每次都在-2处
//处理平衡因子,有三种情况
}
在c中插入时,90平衡因子变为0,60变为0,30变为-1
还有一种极端情况,当h为0时,三个平衡因子都为0(60就是新增的节点)
看最开始插入数据,60的平衡因子会根据插入的不同位置,平衡因子也右不同的变化
当60(subLR)平衡因子为-1时,插入在b中;平衡因子为1时,插入在c中;为0时,h高度为0
void RotaLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotaL(parent->_left);
RotaR(parent);//parent每次都在-2处
//处理平衡因子,有三种情况
if (bf == 1)//新增在
{
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)//新增在
{
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 0)//h == 0
{
parent->_bf = 0;
subL->_bf = 0;
}
else
{
assert(false);
}
subLR->_bf = 0;
}
新节点插入较高右子树的左侧---右左:先右单旋再左单旋
根左右双旋同理,分三种情况的平衡
void RotaRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subL->_left;
int bf = subRL->_bf;
RotaL(parent->_right);
RotaR(parent);//parent每次都在-2处
//处理平衡因子,有三种情况
if (bf == 1)//新增在c
{
parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == -1)//新增在b
{
parent->_bf = 0;
subR->_bf = 1;
}
else if (bf == 0)//h == 0
{
parent->_bf = 0;
subR->_bf = 0;
}
else
{
assert(false);
}
subRL->_bf = 0;
}
insert实现
bool insert(const pair<K,V>& kv)
{
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;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
cur->_parent = parent;
//while控制平衡因子,最坏的情况下,更新到根节点
while (parent)
{
if (parent->_left == cur)
{
parent->_bf--;
}
else if (parent->_right == cur)
{
parent->_bf++;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
parent = parent->_parent;
cur = cur->_parent;//沿着三岔链往上走
}
else if (parent->_bf == 0)
{
break;
}
else if(parent->_bf == 2 || parent->_bf == -2)//开始旋转
{
//左单旋
if (parent->_bf == 2 && cur->_bf == 1)
{
RotaL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotaR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)//左右单旋
{
RotaLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotaRL(parent);
}
else
{
assert(false);//直接报错,平衡因子已经出问题
}
break;
}
else
{
assert(false);//直接报错,插入之前平衡因子已经出问题
}
}
return true;
}
测试二叉树是否为AVL树
中序遍历有序只能代表是搜索树;
通过高度和节点的平衡因子是否计算正确来判断是否为AVL树,如果不是AVL树,大量随机插入后某个节点左右子树高度差>=2,高度差即可和当前节点平衡因子比较。即可判断出是否为AVL树,同时注意需要递归,左右子树也需要保持AVL树。
bool IsBalance()//判断AVL树
{
return _IsBalance(_root);
}
private://需要私有中写子函数,因为需要递归
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int left = height(root->_left);
int right = height(root->_right);
int diff = right - left;
if (abs(diff) >= 2)
return false;
// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
// pRoot平衡因子的绝对值超过1,则一定不是AVL树
if (diff != root->_bf || (diff > 1 || diff < -1))
return false;
return abs(diff) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);//判断AVL树,也要递归判断所有子树是否为AVL树
}
int height(Node* root)
{
if (root == nullptr)
return 0;
int k1 = height(root->_left);
int k2 = height(root->_right);
return max(k1,k2)+1;
}