目录
一、AVL树是什么
1.1 二叉搜索树的定义
在左右子树存在的情况下,左子树的所有值都小于根结点的值,右子树的所有值都大于根节点的值,并且根结点的左右子树也都是二叉搜索树。
1.2 AVL树(平衡二叉树)的定义
定义:二叉搜索树的左右子树的高度差(平衡因子)的绝对值不超过1,且他的左右子树也都是AVL树。
1.3 初始化
AVL树的节点初始化:
template<class T>
struct AVLTreeNode{
AVLTreeNode(const T& data = T())
:_Pparent(nullptr)
,_Pleft(nullptr)
, _Pright(nullptr)
,_bf(0)
, _data(data)
{}
AVLTreeNode<T>* _Pleft; //左子树
AVLTreeNode<T>* _Pright; //右子树
AVLTreeNode<T>* _Pparent; //父节点指针
T _data; //数据
int _bf; //平衡因子
};
AVL树的初始化
template<class T>
class AVLTree{
public:
typedef AVLTreeNode<T> Node
private:
Node* _root //根节点指针
}
二、 实现AVL树(平衡二叉树)
2.1节点插入
在根节点为空的情况下,直接创建一个根节点对其赋值,反之,则进行二叉搜索树的插入。
if (_root == nullptr){
_root = new Node(val);
return true;
}
Node *cur = _root;
Node *parent = nullptr;
while (cur){
parent = cur; //更新父节点
if (cur->_data == val){ //不能插入值相同的元素
return false;
}
else if (cur->_data > val) //插入的值小于目前位置的值,则当前位置更替到左子树,反之更替到右子树
cur = cur->_Pleft;
else
cur = cur->_Pright;
}
cur = new Node(val);
if (parent->_data > val) //根据父节点值判断插入的值该放在哪个位置
parent->_Pleft = cur;
else
parent->_Pright = cur;
cur->_Pparent = parent; //根据节点的结构,需要连接父节点指针
2.2更新平衡因子
当插入新节点之后,部分节点平衡因子一定会发生改变,当插入位置为父亲节点的左孩子时,则该父亲节点的平衡因子会减1,反之加1。
while (parent){
if (parent->_Pleft==cur)
parent->_bf--;
else
parent->_bf++;
if (parent->_bf == 0)
break;
else if (parent->_bf == 1 || parent->_bf == -1){
cur = parent;
parent = parent->_Pparent;
}
这里需要注意的是,当插入一个节点后,父节点的平衡因子变为0时,相当于以该父节点为根的树的高度并没有发生改变,而是在插入节点过程中,该子树的高度差被补齐,不会影响该节点的祖先节点的平衡因子的改变,所以直接跳出循环,平衡因子更新完毕,而当父节点的平衡因子变为-1或者1时,相当于以该父节点为根的树的高度发生了改变,也许会影响该节点的祖先节点的平衡因子的改变,所以继续向上去更新平衡因子,直到某祖先节点平衡因子为0或者直到更新到根节点,则平衡因子更新完毕。
2.3调整二叉搜索树
2.3.1二叉搜索树的旋转
这里我们需要知道,当插入节点之后,某些节点的平衡因子一定会发生改变,而当某个节点的平衡因子的绝对值大于1时,就需要去调整这个二叉搜索树,使之变成一个AVL树,在调整过程中,有左旋、右旋、左右旋、右左旋这几种情况,下面将具体说明。
2.3.1.1右旋
如图:
当出现这种情况(左边的左边高),则需要去将这个二叉搜索树进行右旋。
右旋步骤:将根节点的左孩子作为新的根节点,左孩子的右子树作为根节点的左子树,而根节点作为左孩子的的右孩子。这里我们再画图说明一下(注意节点为一个三向结构体):
代码如下:
void RotateR(Node *parent){
Node *Lcur = parent->_Pleft; //拿到根节点的左孩子
Node *LRcur = Lcur->_Pright; //左孩子的右孩子
parent->_Pleft = LRcur; //左孩子的右子树作为根节点的左子树
if (LRcur)
LRcur->_Pparent = parent;
Lcur->_Pright = parent; //根节点作为左孩子的的右孩子
if (parent == _root){
_root = Lcur;
Lcur->_Pparent = nullptr;
}
else{
Node *pparent = parent->_Pparent;
Lcur->_Pparent = pparent;
if (pparent->_Pleft == parent)
pparent->_Pleft = Lcur;
else
pparent->_Pright = Lcur;
}
parent->_Pparent = Lcur;
parent->_bf = Lcur->_bf = 0; //更新平衡因子
}
这里需要注意,入伙根节点正好是整个树的根节点,则其父亲指针为空,反之需要去得到根节点的父节点,去连接起来。
2.3.1.1左旋
与右旋情况类似,这里如下图(右边的右边高),则需要去左旋。
左旋步骤:将根节点右孩子的左子树作为根节点的右子树,将根节点作为根节点右孩子的左孩子,右孩子作为新的根节点。
如图:
代码如下:
void RotateL(Node* parent){
Node *Rcur = parent->_Pright; //根节点的右孩子
Node *RLcur = Rcur->_Pleft; //根节点右孩子的左孩子
parent->_Pright = RLcur; //将根节点右孩子的左子树作为根节点的右子树
if (RLcur)
RLcur->_Pparent = parent;
Rcur->_Pleft = parent; //将根节点作为根节点右孩子的左孩子
if (parent == _root){
_root = Rcur;
Rcur->_Pparent = nullptr;
}
else{
Node *pparent = parent->_Pparent;
Rcur->_Pparent = pparent;
if (pparent->_Pleft == parent)
pparent->_Pleft = Rcur;
else
pparent->_Pright = Rcur;
}
parent->_Pparent = Rcur;
parent->_bf = Rcur->_bf = 0; //更新平衡因子
}
和右旋类似,这里也需要去判断根节点是否为整个树的根,进行新根节点的连接。
2.3.1.1左右旋
左右旋步骤:当出现这两种情况(左边的右边高)则需要去以根的左孩子为根节点进行左旋,然后再以根为根节点进行右旋。(左旋和右旋在上边已经说过,所以代码实现可以直接套用上述编写的左旋和右旋代码)
具体步骤如下:
2.3.1.1右左旋
满足如下图情况(右边的左边高),则需要进行右左旋。
右左旋步骤:以根的右孩子为根节点进行右旋,然后再以根为根节点进行左旋。
与左右旋的原理类似,所以在这里就不进行图片说明,读者可以自行实现。
2.3.2 总结
当某个节点的平衡因子的绝对值大于1(也就是等于2或者-2)时,只能出现上述的4种情况,我们可以通过当前节点以及当前节点的左孩子或者右孩子的平衡因子进行判断做出相应的旋转。
注意:通过这里的左旋和右旋代码我们可以看出,我们在最后都将当前节点以及当前节点的左孩子或者右孩子的平衡因子更新为了0,但是我们通过画图可以看出,当出现左右旋或者右左旋这两种情况时,设计到的节点的平衡因子不全为0,如我们在左右旋转中所画的具体步骤图就可以看出
在这里我们具体说明如何去纠正:
通过图片可以看出:
左右旋:如果LRcur=-1时,旋转后Lcur=0,root=1;
如果LRcur=1时,旋转后Lcur=-1,root=0;
右左旋:如果RLcur=-1时,旋转后root=0,Rcur=1;
如果RLcur=1时,旋转后root=-1,Rcur=0;
所以左右旋,右左旋代码实现如下:
//左右旋
if (parent->_bf == -2 && cur->_bf == 1){
Node * tmp = cur->_Pright;
int bf = tmp->_bf; //得到LRcur的平衡因子
RotateL(cur); //以根节点的左孩子为根进行左旋
RotateR(parent);//以根节点为根进行右旋
if (bf == 1){
cur->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1){
cur->_bf = 0;
parent->_bf = 1;
}
}
//右左旋
else if (parent->_bf == 2 && cur->_bf == -1){
Node *tmp = cur->_Pleft;
int bf = tmp->_bf; //得到RLcur的平衡因子
RotateR(cur); //以根节点的右孩子为根进行右旋
RotateL(parent); //以根节点为根进行左旋
if (bf == 1){
cur->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1){
cur->_bf = 1;
parent->_bf = 0;
}
}
最后在进行旋转后二叉搜索树一定会变成一个AVL树,所以进行旋转完毕后,则break。
三、AVL树(平衡二叉树)的验证
1.验证是否为二叉搜索树
二叉搜索树的中序遍历为一个有序序列,所以可以通过中序遍历一棵树看是否为二叉搜索树
//搜索二叉树的验证
void inorder(){
_inorder(_root);
cout << endl;
}
void _inorder(Node *root){
if (root){
_inorder(root->_Pleft);
cout << root->_data << " ";
_inorder(root->_Pright);
}
}
2.验证是否为平衡树
平衡树前提是一棵二叉搜索树,所以在满足条件以的基础上,才能进行2操作,验证是否为平衡树需要两步:
1.每个节点的左右子树高度差是否小于2
2.每个节点的平衡因子是否更新正确
//判断是否平衡二叉树
bool isBalance(){
return _isBalance(_root);
}
//树的高度
int Height(Node *root){
if (root == nullptr){
return 0;
}
int left = Height(root->_Pleft);
int right = Height(root->_Pright);
return (left > right ? left : right)+1;
}
bool _isBalance(Node *root){
if (root==nullptr){ //空树为平衡树
return true;
}
if (root->_bf != Height(root->_Pright) - Height(root->_Pleft)) //左右子树的高度差是否等于平衡因子
{
cout << "not balance!" << endl;
return false;
}
return abs(root->_bf) < 2
&& _isBalance(root->_Pleft)
&& _isBalance(root->_Pright);
}