AVL树
AVL树的概念
二叉搜索树随可以缩短查找效率,但是如果数据有序或者接近有序的话讲退化为单支树,查找元素相当于再顺序表中搜索元素,效率低下。因此俄罗斯的科学家发明了AVL树。
AVL树解决上述问题的方法是:当向二叉树中插入新结点后,如果能保证每个结点的左右子树高度差的绝对值不超过1(超过则需要调整),即可降低树的高度,从而减少平衡搜索的长度。
一颗AVL树或者是空树,或者是具有一下性质的二叉搜索树。
1. 它的左右子树都是AVL树
2.左右子树高度之差(平衡因子)的绝对值不超过1(-1/ 0 / 1)。
像上面这颗树就是一颗AVL树。
如果一颗二叉搜索树是高度平衡的,它就是AVL树。如果它有N个节点,那么它的高度可以保持到log_2 N,搜索时间复杂度也是log_2 N。
树节点的定义
template<class k,class v>
struct AVLTreenode
{
//存的是一个键值对
pair<k, v> _kv;
AVLTreenode<k,v>* _left;
AVLTreenode<k, v>* _right;
AVLTreenode<k, v>* _parent;
int _bf; //平衡因子
AVLTreenode(const pair<k,v>& kv)
: _kv(kv)
, _left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
};
我们把节点定义成struct,这样所有的成员都是公有的形式。与二叉搜索树不同的是,我们现在增加了一个平衡因子和一个parent的作用就是去位置左右子树的高度差,是它们的高度差不超过1。
AVL树的插入
AVL树的插入和二叉搜索树的过程过程差不多,只是插入过程中,我们需要去更新平衡因子,进而进行旋转,让整颗树都始终保持着是一颗二叉搜索树的状态。
bool Insert(const pair<k,v>& kv)
{
//首先判空,如果为空,则插入元素就是根节点。
if (_root == nullptr)
{
_root = new node(kv);
return true;
}
//1.先按照二叉搜索树的规则将节点插入到AVL树中
//2. 在插入节点后,AVL树的平衡性会遭到破坏,此时我们需要维护
//我们的平衡因子,并检查是否满足AVL树
//如果cur插入后,parent的平衡因子一定需要调整,因为在插入前,pparent的
//平衡因子一定是-1,0,1 分一下两种情况
// 如果cur插入到parent的左侧,只需要将parent的平衡因子+1
// 如果cur插入到parent的右侧,只需要将parent的平衡因子-1
//此时,我们就需要去判断parent的平衡因子,看是否满足AVL树
//1.如果parent的平衡影子为0,说明插入之前parent是+-1,插入后被调整为了0,此时满足AVL树,插入成功
//2.如果paren的根的树高度增加,需要继续向上更新
//3.如果parent的平衡因子为±2,则parent违反了平衡树的性质,需要对其进行旋转处理
node* parent = nullptr;
node* cur = _root;
//和二叉搜索树一样正常插入
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//走到空了
cur = new node(kv);
if (kv.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
//更新平衡因子
while (parent)
{
if (cur == parent->_left)
parent->_bf--;
else
parent->_bf++;
//检测是否违反平衡树的性质
if (parent->_bf == 0)
break;
else if (parent->_bf == 1 || parent->_bf == -1)
{
//向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转
}
}
return true;
}
AVL树的旋转
如果在一颗原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使整棵树保持平衡。根据节点插入的位置不同,AVL树分为4种:
右单旋
我们看到,上面是右单旋的图,在插入时,AVL树是平衡的,新节点插入到了30的左侧,30左子树增加了一层,导致60这棵树不平衡,要让60平衡,则需要将左边整棵树将一层,并且使整棵树还是保持为AVL数。
可以看到,我们把30的右边给给了60的左边,30的右边是比30大的,比60小的,所以我们把图中 的b 给给60的左时是没有违反平衡树的规则的,此时60再变为30的右,整颗树的旋转就完成了。
我们再来看看平衡因子,这里的整颗树就30,和60需要更新平衡因子。
解释:以上图为例,parent是60,subL是30,subLR是b
注意:如果parent是根节点的话我们就需要让subL成为根,否则的话就继续向上更新就好了。
下面的代码大家可以跟着图对比一下。
void RotatoR(node* parent)
{
//修改节点的指向
node *subL = parent->_left;
node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
node* pparent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//如果parent是根节点,则需要更换更节点
if (pparent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
//parent不是根节点,继续向上更新
else
{
if (pparent->_left == parent)
pparent->_left = subL;
else
pparent->_right = subL;
subL->_parent = pparent;
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}
左单旋
和上面的情况是步骤都是差不多的,首先我们看到60的右边插入了新节点,导致以30为根的树结构的平衡因子变为了2,不平衡了,所以需要旋转,同样的,我们把60的左给给30的右,30再作为60的左,整棵树就平衡了,最后我们再更新平衡因子。
注意:如果parent是根节点的话我们就需要让subR成为根,否则的话就继续向上更新就好了。
void RotatoL(node* parent)
{
node* subR= parent->_right;
node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
node* pparent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (pparent == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == pparent->_left)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
subR->_bf = parent->_bf = 0;
}
左右双旋(先左单旋,再右单旋)
看下上图,如果说我们把新插入节点插在60的左边,这样的话我们仅仅通过通过左单旋或者右单旋是搞不定的(如果说你细心的话,肯定是想到了)。那这种情况该怎么办呢?
这个时候,我们就需要进行左右双旋,首先,我们先以30为根节点,我们对30这颗子树先进行做单旋,旋转成第三幅图这样,我们再将90这棵树进行右单旋就达到了我们想要的结果。最后,我们再将平衡因子更新一下就好了。
注意:这里的平衡因子更新,需要由subLR的平衡因子来决定。如上图(subLR就是60)。
void RotatoLR(node* parent)
{
node* subL = parent->_left;
node* subLR = subL->_right;
int bf = subLR->_bf;
//分别调用我们上面写的左右双旋
RotatoL(subL);
RotatoR(parent);
//对于不同的subLR的平衡因子,他们的更新情况也不同
if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
总结一下:我们直接看图,我们看看subLR(上图也就是60这个节点),我们直接从第一个图看到最后这个结果图,它的整个旋转过程就是把60的左子树分给30的右子树,把60的右子树分给90的左子树,再让90变为整颗树的根。
右左双旋(先右单旋,再左单旋)
因为我们上面已经说过了其实它和左右双旋是差不多的,我们先以90这个节点进行右单旋,再以30这个节点进行做单旋。
同样的,我们直接来看结果,它就是把60的左子树给给了30的右子树,60的右子树给给90 的左子树,然后30,90分别为60的左子树和右子树,60再做整颗树的根。
void RotatoRL(node* parent)
{
node* subR = parent->_right;
node* subRL = subR->_left;
int bf = subRL->_bf;
RotatoR(subR);
RotatoL(parent);
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
代码实现
#pragma once
#include <iostream>
#include<assert.h>
using namespace std;
template<class k,class v>
struct AVLTreenode
{
pair<k, v> _kv;
AVLTreenode<k,v>* _left;
AVLTreenode<k, v>* _right;
AVLTreenode<k, v>* _parent;
int _bf; //平衡因子
AVLTreenode(const pair<k,v>& kv)
: _kv(kv)
, _left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
};
template<class k,class v>
class AVLTree
{
typedef AVLTreenode<k, v> node;
public:
bool Insert(const pair<k,v>& kv)
{
//首先判空,如果为空,则插入元素就是根节点。
if (_root == nullptr)
{
_root = new node(kv);
return true;
}
//1.先按照二叉搜索树的规则将节点插入到AVL树中
//2. 在插入节点后,AVL树的平衡性会遭到破坏,此时我们需要维护
//我们的平衡因子,并检查是否满足AVL树
//如果cur插入后,parent的平衡因子一定需要调整,因为在插入前,pparent的
//平衡因子一定是-1,0,1 分一下两种情况
// 如果cur插入到parent的左侧,只需要将parent的平衡因子+1
// 如果cur插入到parent的右侧,只需要将parent的平衡因子-1
//此时,我们就需要去判断parent的平衡因子,看是否满足AVL树
//1.如果parent的平衡影子为0,说明插入之前parent是+-1,插入后被调整为了0,此时满足AVL树,插入成功
//2.如果paren的根的树高度增加,需要继续向上更新
//3.如果parent的平衡因子为±2,则parent违反了平衡树的性质,需要对其进行旋转处理
node* parent = nullptr;
node* cur = _root;
//和二叉搜索树一样正常插入
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//走到空了
cur = new node(kv);
if (kv.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
//更新平衡因子
while (parent)
{
if (cur == parent->_left)
parent->_bf--;
else
parent->_bf++;
//检测是否违反平衡树的性质
if (parent->_bf == 0)
break;
else if (parent->_bf == 1 || parent->_bf == -1)
{
//向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转
if (parent->_bf == -2 && parent->_left->_bf==-1)
{
//右单旋
RotatoR(parent);
}
else if (parent->_bf == 2 &&parent->_right->_bf== 1)
{
//左单旋
RotatoL(parent);
}
else if (parent->_bf == -2 && parent->_left->_bf==1)
{
//左右双旋
RotatoLR(parent);
}
else if (parent->_bf == 2 && parent->_right->_bf== -1)
{
RotatoRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void RotatoR(node* parent)
{
node *subL = parent->_left;
node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
node* pparent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (pparent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
pparent->_left = subL;
else
pparent->_right = subL;
subL->_parent = pparent;
}
subL->_bf = parent->_bf = 0;
}
void RotatoL(node* parent)
{
node* subR= parent->_right;
node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
node* pparent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (pparent == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == pparent->_left)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
subR->_bf = parent->_bf = 0;
}
void RotatoLR(node* parent)
{
node* subL = parent->_left;
node* subLR = subL->_right;
int bf = subLR->_bf;
RotatoL(subL);
RotatoR(parent);
if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
void RotatoRL(node* parent)
{
node* subR = parent->_right;
node* subRL = subR->_left;
int bf = subRL->_bf;
RotatoR(subR);
RotatoL(parent);
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
void Inorder()
{
_Inorder(_root);
}
int height()
{
return _height(_root);
}
int size()
{
return _size(_root);
}
bool isbalance()
{
return _isbalancetree(_root);
}
private:
node* _root=nullptr;
int _size(node* _root)
{
if (_root == nullptr)
return 0;
return 1 + _size(_root->_left) + _size(_root->_right);
}
bool _isbalancetree(node* _root)
{
if (_root == nullptr)
return true;
int leftheight = _height(_root->_left);
int rightheight = _height(_root->_right);
if (abs(leftheight - rightheight) >= 2)
return false;
return _isbalancetree(_root->_left) && _isbalancetree(_root->_right);
}
int _height(node* _root)
{
if (_root == 0)
{
return 0;
}
int leftheight = _height(_root->_left);
int rightheight = _height(_root->_right);
return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
}
void _Inorder(node* _root)
{
if (_root == nullptr)
return;
_Inorder(_root->_left);
cout << _root->_kv.first << ": " << _root->_kv.second << endl;
_Inorder(_root->_right);
}
};
我们总代码里还包含了一点测试AVL树的高度的代码,大家了解即可,能看懂就看,看不懂也没关系。