天天看点

数据结构之平衡二叉树(AVL树)

定义:称一颗增长二叉树为高度平衡树,当且仅当或由单一外结点组成,或由两个子树形Tl和Tr组成,并且满足:

(1) |h(Tl) - h(Tr)| ≦1,其中h(T)表示树T的高度;

(2) Tl和Tr都是高度平衡树。

由定义可知空树是平衡二叉树。

定义:设T为增长二叉树,q是T之内结点,qL和qR是q的左、右子树,hL和hR分别为qL和qR的高度,q的平衡系数(或称平衡因子)BF(q)定义为hR-hL。对于平衡树中的任一内结点q,则有BF(q)=0, -1或+1。

1 平衡二叉树的结点

相对于二叉查找树,需要增加二叉树的高度这一属性,为的是完成插入和删除过程中对旋转算法。

//AVL树节点信息
template<class T>
class TreeNode
{
    public:
        TreeNode():lson(NULL),rson(NULL),freq(1),hgt(0){}
        T data;//值
        int hgt;//以此节点为根的树的高度
        unsigned int freq;//频率
        TreeNode* lson;//指向左儿子的地址
        TreeNode* rson;//指向右儿子的地址
};
           

2 平衡二叉树类的声明

//AVL树类的属性和方法声明
template<class T>
class AVLTree
{
    private:
        TreeNode<T>* root;//根节点
        void insertpri(TreeNode<T>* &node,T x);//插入
        TreeNode<T>* findpri(TreeNode<T>* node,T x);//查找
        void insubtree(TreeNode<T>* node);//中序遍历
        void Deletepri(TreeNode<T>* &node,T x);//删除
        int height(TreeNode<T>* node);//求树的高度
        void SingRotateLeft(TreeNode<T>* &k2);//左左情况下的旋转
        void SingRotateRight(TreeNode<T>* &k2);//右右情况下的旋转
        void DoubleRotateLR(TreeNode<T>* &k3);//左右情况下的旋转
        void DoubleRotateRL(TreeNode<T>* &k3);//右左情况下的旋转
        int Max(int cmpa,int cmpb);//求最大值
    public:
        AVLTree():root(NULL){}
        void insert(T x);//插入接口
        TreeNode<T>* find(T x);//查找接口
        void Delete(T x);//删除接口
        void traversal();//遍历接口
};
           

3 旋转的分类

旋转可分为四类:左左、左右、右左和右右;

数据结构之平衡二叉树(AVL树)

①左左:图1中,6结点的左子树比右子树高度大2,左子树3结点的左子树高度大于右子树,这种情况称为左左;

②左右:图2中,6结点的左子树比右子树高度大2,左子树3结点的左子树高度小于右子树,这种情况称为左右;

③右左:图3中,2结点的左子树比右子树高度小2,右子树5结点的左子树高度大于右子树,这种情况称为右左;

④右右:图4中,2结点的左子树比右子树高度小2,右子树4结点的左子树高度小于右子树,这种情况称为右右。

从图中可以看出,1和4两种情况是对称的,只需要经过一次旋转就可以完成,我们称之为单旋转;2和3两种情况是对称的,需要经过两次旋转,我们称之为双旋转。

4 单旋转

数据结构之平衡二叉树(AVL树)
//左左情况下的旋转
template<class T>
void AVLTree<T>::SingRotateLeft(TreeNode<T>* &k2)
{
    TreeNode<T>* k1;
    k1=k2->lson;
    k2->lson=k1->rson;
    k1->rson=k2;
    //更新结点K1和K2的高度
    k2->hgt=Max(height(k2->lson),height(k2->rson))+;
    k1->hgt=Max(height(k1->lson),k2->hgt)+;
}
//右右情况下的旋转
template<class T>
void AVLTree<T>::SingRotateRight(TreeNode<T>* &k2)
{
    TreeNode<T>* k1;
    k1=k2->rson;
    k2->rson=k1->lson;
    k1->lson=k2;
    //更新结点K1和K2的高度
    k2->hgt=Max(height(k2->lson),height(k2->rson))+;
    k1->hgt=Max(height(k1->rson),k2->hgt)+;
}
           

5 双旋转

数据结构之平衡二叉树(AVL树)
//左右情况的旋转
template<class T>
void AVLTree<T>::DoubleRotateLR(TreeNode<T>* &k3)
{
    SingRotateRight(k3->lson);
    SingRotateLeft(k3);
}
//右左情况的旋转
template<class T>
void AVLTree<T>::DoubleRotateRL(TreeNode<T>* &k3)
{
    SingRotateLeft(k3->rson);
    SingRotateRight(k3);
}
           

6 插入

插入的方法和二叉查找树基本一样,区别是,插入完成后需要从插入的节点开始维护一个到根节点的路径,每经过一个节点都要维持树的平衡。维持树的平衡要根据高度差的特点选择不同的旋转算法。

//在以node为根结点的二叉平衡树中插入x
template<class T>
void AVLTree<T>::insertpri(TreeNode<T>* &node,T x)
{
    if(node==NULL)//如果结点为空,就在此结点处加入x信息
    {
        node=new TreeNode<T>();
        node->data=x;
        return;
    }
    if(node->data>x)//如果x小于结点的值,就继续在结点的左子树中插入x
    {
        insertpri(node->lson,x);//递归插入
        if((height(node->lson)-height(node->rson))==)//结点左右子树高度差为2时,结点node失去平衡,需要旋转
            if(x < node->lson->data)//x插入在node结点的左子树中
                SingRotateLeft(node);//左左
            else
                DoubleRotateLR(node);//左右
    }
    else if(node->data<x)//如果x大于结点的值,就继续在结点的右子树中插入x
    {
        insertpri(node->rson,x);
        if((height(node->rson)-height(node->lson))==)//结点右左子树高度差为2时,结点node失去平衡,需要旋转
            if(x>node->rson->data)//x插入在node结点的右子树中
                SingRotateRight(node);//右右
            else
                DoubleRotateRL(node);//右左
    }
    else ++(node->freq);//如果相等,就把频率加1
    node->hgt=Max(height(node->lson),height(node->rson));//更新节点node的高度值
}
//插入接口
template<class T>
void AVLTree<T>::insert(T x)
{
    insertpri(root,x);
}
           

7 查找

//查找
template<class T>
TreeNode<T>* AVLTree<T>::findpri(TreeNode<T>* node,T x)
{
    if(node==NULL)//如果节点为空说明没找到,返回NULL
    {
        return NULL;
    }
    if(node->data>x)//如果x小于节点的值,就继续在节点的左子树中查找x
    {
        return findpri(node->lson,x);
    }
    else if(node->data<x)//如果x大于节点的值,就继续在节点的左子树中查找x
    {
        return findpri(node->rson,x);
    }
    else return node;//如果相等,就找到了此节点
}
//查找接口
template<class T>
TreeNode<T>* AVLTree<T>::find(T x)
{
    return findpri(root,x);
}
           

8 删除

删除的方法也和二叉查找树的一致,区别是,删除完成后,需要从删除节点的父亲开始向上维护树的平衡一直到根节点。

//删除
template<class T>
void AVLTree<T>::Deletepri(TreeNode<T>* &node,T x)
{
    if(node==NULL) return ;//没有找到值是x的节点
    if(x < node->data)
    {
         Deletepri(node->lson,x);//如果x小于节点的值,就继续在节点的左子树中删除x
         if((height(node->rson)-height(node->lson))==)//结点node右左子树高度差为2时,结点node失去平衡
            if(node->rson->lson!=NULL&&(height(node->rson->lson)>height(node->rson->rson)) )
                DoubleRotateRL(node);//右左
            else
                SingRotateRight(node);//右右
    }
    else if(x > node->data)
    {
         Deletepri(node->rson,x);//如果x大于节点的值,就继续在节点的右子树中删除x
         if((height(node->lson)-height(node->rson))==)//结点node左右子树高度差为2时,结点node失去平衡
            if(node->lson->rson!=NULL&& (height(node->lson->rson)>height(node->lson->lson) ))
                DoubleRotateLR(node);//左右
            else
                SingRotateLeft(node);//左左
    }
    else//如果相等,此节点就是要删除的节点
    {
        if(node->lson&&node->rson)//此节点有两个儿子
        {
            TreeNode<T>* temp=node->rson;//temp指向节点的右儿子
            while(temp->lson!=NULL) temp=temp->lson;//找到右子树中值最小的节点
            //把右子树中最小节点的值赋值给本节点
            node->data=temp->data;
            node->freq=temp->freq;
            Deletepri(node->rson,temp->data);//删除右子树中最小值的节点
            if(==height(node->lson)-height(node->rson))
            {
                if(node->lson->rson!=NULL&& (height(node->lson->rson)>height(node->lson->lson) ))
                    DoubleRotateLR(node);
                else
                    SingRotateLeft(node);
            }
        }
        else//此节点有1个或0个儿子
        {
            TreeNode<T>* temp=node;
            if(node->lson==NULL)//有右儿子或者没有儿子
            node=node->rson;
            else if(node->rson==NULL)//有左儿子
            node=node->lson;
            delete(temp);
            temp=NULL;
        }
    }
    if(node==NULL) return;
    node->hgt=Max(height(node->lson),height(node->rson))+;
    return;
}
//删除接口
template<class T>
void AVLTree<T>::Delete(T x)
{
    Deletepri(root,x);
}
           

9 判断是否为平衡二叉树

求树的深度:

struct BinaryTreeNode  
{  
    int m_Value;  
    BinaryTreeNode* m_pLeft;  
    BinaryTreeNode* m_pRight;  
};  

int TreeDepth(BinaryTreeNode* pRoot)  
{  
    if (pRoot == NULL)  
        return ;  

    int nLeftDepth = TreeDepth(pRoot->m_pLeft);  
    int nRightDepth = TreeDepth(pRoot->m_pRight);  

    return (nLeftDepth>nRightDepth)?(nLeftDepth+):(nRightDepth+);  
}  
           

递归判断是否为平衡二叉树

bool IsBalanced(BinaryTreeNode* pRoot)  
{  
    if(pRoot== NULL) //空树是平衡二叉树 
        return true;  

    int nLeftDepth = TreeDepth(pRoot->m_pLeft);  
    int nRightDepth = TreeDepth(pRoot->m_pRight);  
    int diff = nRightDepth-nLeftDepth;  

    if (diff> || diff<-)//左右子树高度差的绝对值要小于等于1  
        return false;  

    return IsBalanced(pRoot->m_pLeft)&&IsBalanced(pRoot->m_pRight);  
}  
           

继续阅读