查找算法 | 平衡二叉树(AVL树)详细分析

  • Post author:
  • Post category:其他




AVL:完全平衡的二叉查找树


二叉查找树可以表示动态的数据集合,对于给定的数据集合,在建立一颗二叉查找树时,

二叉查找树的结构形态与关键字的插入顺序有关

。如果全部或者部分地按照关键字的递增或者递减顺序插入二叉查找树的结点,则所建立的二叉查找树全部或者在局部形成退化的单分支结构。在最坏的情况下,二叉查找树可能完全偏斜,

高度为n

,其平均与最坏的情况下

查找时间都是O(n)

;而最好的情况下,二叉查找树的结点尽可能靠近根结点,其平均与最好情况的查找时间都是O(logn)。因此,我们希望最理想的状态下是使二叉查找树始终处于良好的结构形态。(图片有点问题,意会就好)



1962年,Adelson-Velsikii和Landis提出了一种结点在高度上相对平衡的二叉查找树,又称为AVL树。其平均和最坏情况下的

查找时间都是O(logn)

。同时,

插入和删除的时间复杂性也会保持O(logn)

,且在插入和删除之后,在高度上仍然保持平衡。


AVL树

又称为

平衡二叉树

,即Balanced Binary Tree或者Height-Balanced Tree,它或者是一棵空二叉树,或者是具有如下性质的二叉查找树:

  • 左子树和右子树都是高度平衡的二叉树;
  • 左子树和右子树的高度之差的绝对值不超过1。

如果将二叉树上结点的

平衡因子

BF(Balanced Factor)定义为该结点的左子树与右子树的高度之差,根据AVL树的定义,AVL树中的任意结点的平衡因子只可能是-1(右子树高于左子树)、0或者1(左子树高于右子树),在某些图中也会表示为绝对高度差,即0,1,2这种形式,请注意理解。

BalanceFactor = height(left-sutree) − height(right-sutree)





Rebalance:平衡调整


AVL树的调整过程很类似于数学归纳法,每次在插入新节点之后都会找到

离新插入节点最近的非平衡叶节点

,然后对其进行旋转操作以使得树中的每个节点都处于平衡状态。



Left Rotation:左旋,右子树右子节点

当新插入的结点为右子树的右子结点时,我们需要进行左旋操作来保证此部分子树继续处于平衡状态。



我们应该

找到离新插入的结点最近的一个非平衡结点,来以其为轴进行旋转

,下面看一个比较复杂的情况:





Right Rotation:右旋,左子树左子节点

当新插入的结点为左子树的左子结点时,我们需要进行右旋操作来保证此部分子树继续处于平衡状态。



下面看一个比较复杂的情况:





Left-Right Rotation:先左旋再右旋,左子树右子节点

在某些情况下我们需要进行两次旋转操作,譬如在如下的情况下,某个结点被插入到了左子树的右子结点:



我们首先要以A为轴进行左旋操作,然后需要以C为轴进行右旋操作,最终得到的又是一棵平衡树。

下面看一个比较复杂的情况:




注意

:这里还有另外一种情况没有画出来,就是新插入的节点为45的左孩子。大家自己拿笔和纸画一画。



Right-Left Rotation:先右旋再左旋,右子树左子节点






注意:这里还有另外一种情况没有画出来,就是新插入的节点为55的右孩子。大家自己拿笔和纸画一画。



构建平衡二叉树的代码实现


#include <stdio.h>
#include <stdlib.h>

//分别定义平衡因子数
#define LH +1
#define EH  0
#define RH -1

typedef int ElemType;
typedef enum {false,true} bool;

//定义二叉排序树
typedef struct BSTNode{
    ElemType data;	//节点存储的数据
    int bf;	//平衡标志
    struct BSTNode *lchild,*rchild;
}*BSTree,BSTNode;

//对以 p 为根结点(以 p 为轴)的二叉树做右旋处理,令 p 指针指向新的树根结点
void R_Rotate(BSTree* p)
{
	// 令 p 的左孩子的右孩子作为 p 的左孩子
    BSTree lc = (*p)->lchild;
    (*p)->lchild = lc->rchild;
	//令 p 作为 p 的左孩子的右孩子
    lc->rchild = *p;
	// 令 p 的左孩子作为新的根节点
    *p = lc;
}

//对以 p 为根结点(以 p 为轴)的二叉树做左旋处理,令 p 指针指向新的树根结点
void L_Rotate(BSTree* p)
{
    // 令 p 的右孩子的左孩子作为 p 的右孩子
    BSTree rc = (*p)->rchild;
    (*p)->rchild = rc->lchild;
	//令 p 作为 p 的右孩子的左孩子
    rc->lchild = *p;
	// 令 p 的右孩子作为新的根节点
    *p = rc;
}

//对以指针 T 所指向结点为根结点的二叉树作左子树的平衡处理,令指针 T 指向新的根结点
void LeftBalance(BSTree* T)
{
    BSTree lc,rd;
    lc = (*T)->lchild;
	//进入此方法前就已经判断为左旋了,还需要确定是哪种左旋:1、直接左旋,2、先左旋后右旋
    //查看以 T 的左子树为根结点的子树,失去平衡的原因:
    //  如果 bf 值为 1 ,则说明添加在左子树为根结点的左子树中,需要对其进行右旋处理;
    //  反之,如果 bf 值为 -1,说明添加在以左子树为根结点的右子树中,需要进行双向先左旋后右旋的处理
    switch (lc->bf)
    {
        case LH:
            (*T)->bf = lc->bf = EH;
            R_Rotate(T);
            break;
        case RH:
            rd = lc->rchild;
            switch(rd->bf)	//这部分不好理解,拿笔在纸上画一画就特别好理解了(这部分的情况在前面的讲解里没有)
		{
			case LH:
				(*T)->bf = RH;
				lc->bf = EH;
				break;
			case EH:
				(*T)->bf = lc->bf = EH;
				break;
			case RH:
				(*T)->bf = EH;
				lc->bf = LH;
				break;
		}
            rd->bf = EH;
            L_Rotate(&(*T)->lchild);
            R_Rotate(T);
            break;
    }
}
//右子树的平衡处理同左子树的平衡处理完全类似
void RightBalance(BSTree* T)
{
    BSTree lc,rd;
    lc= (*T)->rchild;
    switch (lc->bf)
    {
        case RH:
            (*T)->bf = lc->bf = EH;
            L_Rotate(T);
            break;
        case LH:
            rd = lc->lchild;
            switch(rd->bf)
		{
			case LH:
				(*T)->bf = EH;
				lc->bf = RH;
				break;
			case EH:
				(*T)->bf = lc->bf = EH;
				break;
			case RH:
				(*T)->bf = EH;
				lc->bf = LH;
				break;
		}
            rd->bf = EH;
            R_Rotate(&(*T)->rchild);
            L_Rotate(T);
            break;
    }
}

//taller 用于记录插入新节点后是否使相应的子树高度增加,从而根据平衡因子判断是否需要対树进行平衡
//算法使用了递归,插入完成后,逐层向上对相应子树进行处理
//无返回值的地方,统一在最后 return 1;
int InsertAVL(BSTree* T,ElemType e,bool* taller)
{
    //如果本身为空树(树的根节点或相应子树为空),则直接添加 e 为根结点
    if ((*T)==NULL)
    {
        (*T)=(BSTree)malloc(sizeof(BSTNode));
        (*T)->bf = EH;
        (*T)->data = e;
        (*T)->lchild = NULL;
        (*T)->rchild = NULL;
        *taller=true;
    }
    //如果二叉排序树中已经存在 e ,则不做任何处理
    else if (e == (*T)->data)
    {
        *taller = false;
        return 0;
    }
    //如果 e 小于结点 T 的数据域,则插入到 T 的左子树中
    else if (e < (*T)->data)
    {
        //如果插入过程,不会影响树本身的平衡,则直接结束
        if(!InsertAVL(&(*T)->lchild,e,taller))
            return 0;
        //判断插入过程是否会导致整棵树的深度 +1
        if(*taller)
        {
            //判断根结点 T 的平衡因子是多少,由于是在其左子树添加新结点的过程中导致失去平衡,
			//所以当 T 结点的平衡因子本身为 1(左高)时,需要进行左子树的平衡处理,否则更新树中各结点的平衡因子数
            switch ((*T)->bf)
            {
                case LH:
                    LeftBalance(T);	// 左旋対树进行平衡,平衡后树的高度不会增加
                    *taller = false;
                    break;
                case EH:
                    (*T)->bf = LH;	//更新平衡因子
                    *taller = true;
                    break;
                case RH:
                    (*T)->bf = EH;	//更新平衡因子
                    *taller = false;
                    break;
            }
        }
    }
    //同样,当 e>T->data 时,需要插入到以 T 为根结点的树的右子树中,同样需要做和以上同样的操作
    else
    {
        if(!InsertAVL(&(*T)->rchild,e,taller))
            return 0;
        if (*taller)
        {
            switch ((*T)->bf)
            {
                case LH:
                    (*T)->bf = EH;
                    *taller = false;
                    break;
                case EH:
                    (*T)->bf = RH;
                    *taller = true;
                    break;
                case RH:
                    RightBalance(T);
                    *taller = false;
                    break;
            }
        }
    }
    return 1;
}

//判断现有平衡二叉树中是否已经具有数据域为 e 的结点
bool FindNode(BSTree root,ElemType e,BSTree* pos)
{
    BSTree pt = root;
    (*pos) = NULL;
    while(pt)
    {
        if (pt->data == e)
        {
            //找到节点,pos指向该节点并返回true
            (*pos) = pt;
            return true;
        }
        else if (pt->data>e)
        {
            pt = pt->lchild;
        }
        else
            pt = pt->rchild;
    }
    return false;
}

//中序遍历平衡二叉树
void InorderTra(BSTree root)
{
    if(root->lchild)
        InorderTra(root->lchild);
   
    printf("%d ",root->data);
   
    if(root->rchild)
        InorderTra(root->rchild);
}

int main()
{
    int i,nArr[] = {1,23,45,34,98,9,4,35,23};
    BSTree root=NULL,pos;
    bool taller;
    //用 nArr查找表构建平衡二叉树(不断插入数据的过程)
    for (i=0;i<9;i++)
    {
        InsertAVL(&root,nArr[i],&taller);
    }
    //中序遍历输出
    InorderTra(root);
    //判断平衡二叉树中是否含有数据域为 103 的数据
    if(FindNode(root,103,&pos))
        printf("\n%d\n",pos->data);
    else
        printf("\nNot find this Node\n");
    return 0;
}



版权声明:本文为liupeifeng3514原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。