zoukankan      html  css  js  c++  java
  • 二叉平衡树的插入和删除操作

    1.      二叉平衡树

    二叉排序树查找、插入和删除操作的时间复杂度和树的深度n有关。构建树时,当先后插入的结点按关键字有序时,二叉排序树退化为单枝树,平均查找长度为(n+1)/2,查找效率比较低。提高查找效率,关键在于最大限度地降低树的深度n。因此需要在构建二叉排序树的过程中进行平衡化处理,使之成为二叉平衡树。

    二叉平衡树,又称AVL树。它或者是一棵空树,或者是具有下列性质的树:

    1)      具备二叉排序树的所有性质;

    2)      左子树和右子树深度差的绝对值不超过1

    3)      左子树和右子树都是二叉平衡树。

    二叉平衡树结点的平衡因子定义为左子树与右子树的深度之差。二叉平衡树结点的平衡因子只可能取-101三个值。含有n个结点的二叉平衡树的深度与logn同数量级,平均查找长度也和logn同数量级。

    二叉平衡树采用二叉链表的结构进行存储。结构体中增加结点的高度,用以计算结点的平衡因子。结点高度定义:空结点的高度为0;非空结点的高度为以该结点为根结点的树的高度。

    二叉链表:

    /* 
     * 二叉树的二叉链表存储结构。
     * 额外添加树的高度,以判断结点的平衡度。
     */
    typedef int TElemType;
    typedef struct BiNode
    {
    	TElemType data;
    	struct BiNode *lchild;
    	struct BiNode *rchild;
    	int height;
    }BiNode, *BiTree;

    结点高度:

    /*
     * 当T=NULL ,即树为空树时,无法通过T->height获取树的高度0,所以要额外编写该函数。
     */
    int GetHeight(BiTree T)
    {
    	if (T)
    		return T->height;
    	return 0;
    }

    2.      处理失衡的四种旋转方式

    如何在插入结点的时候进行“平衡化”处理?当在树中插入一个结点时,检查树是否因插入操作而失衡,若失衡,则找出其中的最小不平衡二叉树,对最小不平衡二叉树进行调整,以达到新的平衡。最小不平衡二叉树定义为以离插入结点最近,且平衡因子绝对值大于1的结点作为根结点的树。

    对最小不平衡树进行调整的操作是旋转,共有4种旋转方式LL型,LR型,RL型,RR型,分别介绍如下:

    1)        LL(单次右旋)

    当根结点左子树的左子树中的节点导致根结点的平衡因子为2时,采用LL型旋转进行调整。图示为两种需进行单次右旋的不平衡树。

    clip_image002clip_image004

    LL型旋转即单次右旋,是将根结点的左孩子作为新的根结点,根结点左孩子的右子树作为老根结点的左子树。图示如下:

    clip_image006

    注意:旋转之后,整个树中只有结点k1,k2的高度发生变化,而x,y,z三棵子树中所有结点的高度均未发生变化。

    代码:

    /*
     * 当T的左子树的左子树上的节点使得T的平衡度为2时,以T为中心进行右旋。
     */
    bool LLRotate(BiTree *T)
    {
    	BiTree lc;
    	lc = (*T)->lchild;
    	(*T)->lchild = lc->rchild;
    	lc->rchild = (*T);
    
    	//注意要更新结点的高度。整个树中只有*T的左子树和lc的右子树发生了变化,所以只需更改这两棵树的高度。
    	(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    	lc->height = max(GetHeight(lc->lchild), GetHeight(lc->rchild)) + 1;
    
    	*T = lc;
    	return true;
    }

    2)        RR(单次左旋)

    当根结点右子树的右子树中的节点导致根结点的平衡因子为-2时,采用RR型旋转进行调整。图示为两种需进行单次左旋的不平衡树。RR型旋转与LL型旋转相对称。

    clip_image008

    RR型旋转即单次左旋,是将根结点的右孩子作为新的根结点,根结点右孩子的左子树作为老根结点的右子树。图示如下:

    clip_image012

    注意:旋转之后,整个树中只有结点k1,k2的高度发生变化,而x,y,z三棵子树中所有结点的高度均未发生变化。

    代码:

    /*
     * 当T的右子树的右子树上的节点使得T的平衡度为-2时,以T为中心进行左旋。
     */
    bool RRRotate(BiTree *T)
    {
    	BiTree rc;
    	rc = (*T)->rchild;
    	(*T)->rchild = rc->lchild;
    	rc->lchild = (*T);
    
    	//注意要更新结点的高度。整个树中只有*T的左子树和lc的右子树发生了变化,所以只需更改这两棵树的高度。
    	(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    	rc->height = max(GetHeight(rc->lchild), GetHeight(rc->rchild)) + 1;
    
    	*T = rc;
    	return true;
    }

    3)        LR(先单次左旋,再单次右旋)

    当根结点左子树的右子树中的节点导致根结点的平衡因子为2时,采用LR型旋转进行调整。图示为两种需进行LR型旋转的不平衡树。

    clip_image014clip_image016

    LR型旋转是先以根结点的左孩子为中心进行单次左旋,再以根结点为中心进行单次右旋。图示如下:

    代码:

    /*
     * 当T的左子树的右子树上的节点使得T的平衡度为2时,
     * 先以T的左子树为中心进行左旋,再以T为中心进行右旋。
     */
    bool LRRotate(BiTree *T)
    {
    	RRRotate(&((*T)->lchild));
    	LLRotate(T);
    	return true;
    }

    4)        RL型(先单次右旋,再单次左旋)

    当根结点右子树的左子树中的节点导致根结点的平衡因子为-2时,采用RL型旋转进行调整。图示为两种需进行RL型旋转的不平衡树。RL型旋转与LR型旋转相对应。

    clip_image020clip_image022

    RL型旋转是先以根结点的右孩子为中心进行单次右旋,再以根结点为中心进行单次左旋。图示如下:

    代码:

    /*
     * 当T的右子树的左子树上的节点使得T的平衡度为-2时,
     * 先以T的右子树为中心进行右旋,再以T为中心进行左旋。
     */
    bool RLRotate(BiTree *T)
    {
    	LLRotate(&((*T)->rchild));
    	RRRotate(T);
    	return true;
    }

    3.      插入操作

    插入操作的代码如下。

    /*
     * 插入操作。
     * 如果以*T为根结点的二叉平衡树中已有结点key,插入失败,函数返回FALSE;
     * 否则将结点key插入到树中,插入结点后的树仍然为二叉平衡树,函数返回TRUE。
     */
    bool AVLInsert(BiTree *T, TElemType key)
    {
    	BiTree t;
    
    	//如果当前查找的根结点为空树,表明查无此结点,故插入结点。
    	if (!*T)
    	{
    		t = (BiTree)malloc(sizeof(BiNode));
    		t->data = key;
    		t->height = 1;
    		t->lchild = NULL;
    		t->rchild = NULL;
    		*T = t;
    		return true;
    	}
    	//已有此结点,不再插入。
    	else if (key == (*T)->data)
    	{
    		return false;
    	}
    	//在左子树中递归插入。
    	else if (key < (*T)->data)
    	{
    		if (!AVLInsert(&((*T)->lchild), key))
    			return false;
    		else
    		{
    			//插入成功,修改树的高度。
    			(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    
    			//已在*T的左子树插入结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
    			if (2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
    			{
    				//在左子树的左子树中插入结点。
    				if (GetHeight((*T)->lchild->lchild) > GetHeight((*T)->lchild->rchild))
    				{
    					LLRotate(T);
    				}
    				//在左子树的右子树中插入结点。
    				else
    				{
    					LRRotate(T);
    				}
    			}
    			return true;
    		}
    	}
    	//在右子树中递归插入。
    	else // (key > (*T)->data)
    	{
    		if (!AVLInsert(&(*T)->rchild, key))
    			return false;
    		else
    		{
    			//插入成功,修改树的高度。
    			(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    
    			//已在*T的右子树插入结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
    			if (-2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
    			{
    				//在右子树的左子树中插入结点。
    				if (GetHeight((*T)->rchild->lchild) > GetHeight((*T)->rchild->rchild))
    				{
    					RLRotate(T);
    				}
    				//在右子树的右子树中插入结点。
    				else
    				{
    					RRRotate(T);
    				}
    			}
    			return true;
    		}
    	}
    }
    

    以下图为例进行两个关键点的说明:进行旋转的树为最小不平衡二叉树;插入结点之后父结点高度的递归修正。

    假如要在图一二叉平衡树中插入结点1

    函数调用步骤:

    1

    调用函数AVLInsert(&9,1)(为表述方便,以&9代表指向结点9的指针)

    2

    由于1<9,继续调用AVLInsert(&7,1)

    3

    由于1<7,继续调用AVLInsert(&3,1)

    4

    由于1<3,继续调用AVLInsert(&2,1)

    5

    由于2<1,继续调用AVLInsert(NULL,1),此时由于*T为空树,增加结点1,并把结点1的高度设置为1,左右孩子分别为空树,如图二所示。函数返回TRUE

    6

    AVLInsert(NULL,1)函数返回TRUE,并返回至AVLInsert(&2,1),因插入成功,所以更新结点2的高度为max(1,0)+1=2,结点2的平衡因子为1,不进行旋转操作,函数返回TRUE

    7

    AVLInsert(&2,1)函数返回TRUE,并返回至AVLInsert(&3,1),因插入成功,所以更新结点3的高度为max(2,1)+1=3,结点3的平衡因子为1,不进行旋转操作,函数返回TRUE

    8

    AVLInsert(&3,1)函数返回TRUE,并返回至AVLInsert(&7,1),因插入成功,所以更新结点7的高度为max(3,1)+1=4,结点7的平衡因子为2,进行旋转操作,旋转之后,更新结点3的高度为2,结点7的高度为2,如图三所示。函数返回TRUE

    9

    AVLInsert(&7,1)函数返回TRUE,并返回至AVLInsert(&9,1),因插入成功,所以更新结点9的高度为max(2,1)+1=4,结点9的平衡因子为1,不进行旋转操作,函数返回TRUE,插入过程结束。

    插入的结点一定为叶子结点,插入结点之后依次进行递归调用的返回操作,在返回之后,修正父结点的高度(2->3->7->9),之后判断父结点的平衡因子,当平衡因子超范围(结点7)时,以该结点为根结点的树为最小不平衡二叉树,此时进行旋转操作。当AVLInsert(&7,1)函数返回之后,以结点7的父结点9为根结点的树将不再需要进行旋转操作。因此每次通过函数AVLInsert()插入一个结点时,旋转操作只在最小不平衡二叉树中进行一次。已插入结点的父结点的高度是在递归过程中依次进行修正的。

    4.      删除操作

    删除操作的代码如下。

    /*
     * 删除操作。
     * 如果以*T为根结点的树中存在结点key,将结点删除,函数返回TRUE,
     * 否则删除失败,函数返回FALSE。
     */
    bool AVLDelete(BiTree *T, TElemType key)
    {
    	BiTree pre, post;
    
    	//没有找到该结点。
    	if (!*T)
    		return false;
    	//找到结点,将它删除。
    	else if (key == (*T)->data)
    	{
    		//待删除节点为叶子结点。
    		if (!(*T)->lchild && !(*T)->rchild)
    			*T = NULL;
    		//待删除结点只有右孩子。
    		else if (!(*T)->lchild)
    			*T = (*T)->rchild;
    		//待删除结点只有左孩子。
    		else if (!(*T)->rchild)
    			*T = (*T)->lchild;
    		//待删除结点既有左孩子,又有右孩子。
    		else
    		{
    			//当待删除结点*T左子树的高度大于右子树的高度时,用*T的前驱结点pre代替*T,
    			//再将结点pre从树中删除。这样可以保证删除结点后的树仍为二叉平衡树。
    			if (GetHeight((*T)->lchild) > GetHeight((*T)->rchild))
    			{
    				//寻找前驱结点pre。
    				pre = (*T)->lchild;
    				while (pre->rchild)
    				{
    					pre = pre->rchild;
    				}
    				//用pre替换*T。
    				(*T)->data = pre->data;
    				
    				//删除节点pre。
    				//虽然能够确定pre所属最小子树的根结点为&pre,
    				//但是不采用AVLDelete(&pre,pre->data)删除pre,目的是方便递归更改节点的高度。
    				AVLDelete(&((*T)->lchild), pre->data);
    			}
    			//当待删除结点*T左子树的高度小于或者等于右子树的高度时,用*T的后继结点post代替*T,
    			//再将结点post从树中删除。这样可以保证删除结点后的树仍为二叉平衡树。
    			else
    			{
    				//寻找后继节点post。
    				post = (*T)->rchild;
    				while (post->lchild)
    					post = post->lchild;
    				//用post替换*T。
    				(*T)->data = post->data;
    
    				//删除节点post。
    				//虽然能够确定post所属最小子树的根结点为&post,
    				//但是不采用AVLDelete(&post,post->data)删除post,目的是方便递归更改节点的高度。
    				AVLDelete(&((*T)->rchild), post->data);
    			}
    		}
    		return true;
    	}
    	//在左子树中递归删除。
    	else if (key < (*T)->data)
    	{
    		if (!AVLDelete(&((*T)->lchild), key))
    			return false;
    		else
    		{
    			//删除成功,修改树的高度。
    			(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    			//已在*T的左子树删除结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
    			if (-2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
    			{
    				if (GetHeight((*T)->rchild->lchild) > GetHeight((*T)->rchild->rchild))
    				{
    					RLRotate(T);
    				}
    				else
    				{
    					RRRotate(T);
    				}
    			}
    			return true;
    		}
    	}
    	//在右子树中递归删除。
    	else
    	{
    		if (!AVLDelete(&((*T)->rchild), key))
    			return false;
    		else
    		{
    			//删除成功,修改树的高度。
    			(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    			//已在*T的右子树删除结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
    			if (2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
    			{
    				if (GetHeight((*T)->lchild->lchild) > GetHeight((*T)->lchild->rchild))
    				{
    					LLRotate(T);
    				}
    				else
    				{
    					LRRotate(T);
    				}
    			}
    			return true;
    		}
    	}
    }

    关键点:

    1. 当待删除结点*T既有左子树又有右子树且左子树高度大于右子树高度时,用结点*T的前驱结点pre替换*T,之后再删除前驱结点pre;当右子树高度大于左子树高度时,用结点*T的后继节点post替换结点*T,之后再删除后继结点post。这样可以保证在删除操作之后,树在不进行旋转操作的情况下仍为二叉平衡树。

    2.   在删除前驱结点pre和后继结点post时,使用AVLDelete(&((*T)->lchild), pre->data)AVLDelete(&((*T)->rchild), post->data)。这样可以保证被删除结点prepost的父节点直至根结点的结点高度都会被递归修正一次。结点高度的递归修正同插入操作。

    5.      时间复杂度

    在平衡树上进行查找的过程和排序树相同,因此在查找过程中和给定值进行比较的关键字个数不超过树的深度。假设F(N)表示N层平衡二叉树的最少结点个数,则F[1]=1F[2]=2F(N)=F(N-2)+F(N-1)+1。在平衡树上进行查找的时间复杂度为O(logn)

    6.      完整源代码

    #ifndef AVL_H
    #define AVL_H
    
    #include <stdio.h>
    #include <stdlib.h>    //内含max(a,b)宏,故不需自己编写。
    #include <stdbool.h>
    
    /* 
     * 二叉树的二叉链表存储结构。
     * 额外添加树的高度,以判断结点的平衡度。
     */
    typedef int TElemType;
    typedef struct BiNode
    {
        TElemType data;
        struct BiNode *lchild;
        struct BiNode *rchild;
        int height;
    }BiNode, *BiTree;
    
    //函数声明
    bool AVLInsert(BiTree *T, TElemType key);
    bool AVLDelete(BiTree *T, TElemType key);
    bool LLRotate(BiTree *T);
    bool LRRotate(BiTree *T);
    bool RLRotate(BiTree *T);
    bool RRRotate(BiTree *T);
    int GetHeight(BiTree T);
    
    #endif
    AVL.h
    /*
     * 实现二叉平衡树的插入、删除操作
     */
    
    #include "AVL.h"
    
    /*
     * 插入操作。
     * 如果以*T为根结点的二叉平衡树中已有结点key,插入失败,函数返回FALSE;
     * 否则将结点key插入到树中,插入结点后的树仍然为二叉平衡树,函数返回TRUE。
     */
    bool AVLInsert(BiTree *T, TElemType key)
    {
        BiTree t;
    
        //如果当前查找的根结点为空树,表明查无此结点,故插入结点。
        if (!*T)
        {
            t = (BiTree)malloc(sizeof(BiNode));
            t->data = key;
            t->height = 1;
            t->lchild = NULL;
            t->rchild = NULL;
            *T = t;
            return true;
        }
        //已有此结点,不再插入。
        else if (key == (*T)->data)
        {
            return false;
        }
        //在左子树中递归插入。
        else if (key < (*T)->data)
        {
            if (!AVLInsert(&((*T)->lchild), key))
                return false;
            else
            {
                //插入成功,修改树的高度。
                (*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    
                //已在*T的左子树插入结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
                if (2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
                {
                    //在左子树的左子树中插入结点。
                    if (GetHeight((*T)->lchild->lchild) > GetHeight((*T)->lchild->rchild))
                    {
                        LLRotate(T);
                    }
                    //在左子树的右子树中插入结点。
                    else
                    {
                        LRRotate(T);
                    }
                }
                return true;
            }
        }
        //在右子树中递归插入。
        else // (key > (*T)->data)
        {
            if (!AVLInsert(&(*T)->rchild, key))
                return false;
            else
            {
                //插入成功,修改树的高度。
                (*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
    
                //已在*T的右子树插入结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
                if (-2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
                {
                    //在右子树的左子树中插入结点。
                    if (GetHeight((*T)->rchild->lchild) > GetHeight((*T)->rchild->rchild))
                    {
                        RLRotate(T);
                    }
                    //在右子树的右子树中插入结点。
                    else
                    {
                        RRRotate(T);
                    }
                }
                return true;
            }
        }
    }
    
    /*
     * 删除操作。
     * 如果以*T为根结点的树中存在结点key,将结点删除,函数返回TRUE,
     * 否则删除失败,函数返回FALSE。
     */
    bool AVLDelete(BiTree *T, TElemType key)
    {
        BiTree pre, post;
    
        //没有找到该结点。
        if (!*T)
            return false;
        //找到结点,将它删除。
        else if (key == (*T)->data)
        {
            //待删除节点为叶子结点。
            if (!(*T)->lchild && !(*T)->rchild)
                *T = NULL;
            //待删除结点只有右孩子。
            else if (!(*T)->lchild)
                *T = (*T)->rchild;
            //待删除结点只有左孩子。
            else if (!(*T)->rchild)
                *T = (*T)->lchild;
            //待删除结点既有左孩子,又有右孩子。
            else
            {
                //当待删除结点*T左子树的高度大于右子树的高度时,用*T的前驱结点pre代替*T,
                //再将结点pre从树中删除。这样可以保证删除结点后的树仍为二叉平衡树。
                if (GetHeight((*T)->lchild) > GetHeight((*T)->rchild))
                {
                    //寻找前驱结点pre。
                    pre = (*T)->lchild;
                    while (pre->rchild)
                    {
                        pre = pre->rchild;
                    }
                    //用pre替换*T。
                    (*T)->data = pre->data;
                    
                    //删除节点pre。
                    //虽然能够确定pre所属最小子树的根结点为&pre,
                    //但是不采用AVLDelete(&pre,pre->data)删除pre,目的是方便递归更改节点的高度。
                    AVLDelete(&((*T)->lchild), pre->data);
                }
                //当待删除结点*T左子树的高度小于或者等于右子树的高度时,用*T的后继结点post代替*T,
                //再将结点post从树中删除。这样可以保证删除结点后的树仍为二叉平衡树。
                else
                {
                    //寻找后继节点post。
                    post = (*T)->rchild;
                    while (post->lchild)
                        post = post->lchild;
                    //用post替换*T。
                    (*T)->data = post->data;
    
                    //删除节点post。
                    //虽然能够确定post所属最小子树的根结点为&post,
                    //但是不采用AVLDelete(&post,post->data)删除post,目的是方便递归更改节点的高度。
                    AVLDelete(&((*T)->rchild), post->data);
                }
            }
            return true;
        }
        //在左子树中递归删除。
        else if (key < (*T)->data)
        {
            if (!AVLDelete(&((*T)->lchild), key))
                return false;
            else
            {
                //删除成功,修改树的高度。
                (*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
                //已在*T的左子树删除结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
                if (-2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
                {
                    if (GetHeight((*T)->lchild->lchild) > GetHeight((*T)->lchild->rchild))
                    {
                        LLRotate(T);
                    }
                    else
                    {
                        LRRotate(T);
                    }
                }
                return true;
            }
        }
        //在右子树中递归删除。
        else
        {
            if (!AVLDelete(&((*T)->rchild), key))
                return false;
            else
            {
                //删除成功,修改树的高度。
                (*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
                //已在*T的右子树删除结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
                if (2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
                {
                    if (GetHeight((*T)->rchild->lchild) > GetHeight((*T)->rchild->rchild))
                    {
                        RLRotate(T);
                    }
                    else
                    {
                        RRRotate(T);
                    }
                }
                return true;
            }
        }
    }
    
    /*
     * 当T的左子树的左子树上的节点使得T的平衡度为2时,以T为中心进行右旋。
     */
    bool LLRotate(BiTree *T)
    {
        BiTree lc;
        lc = (*T)->lchild;
        (*T)->lchild = lc->rchild;
        lc->rchild = (*T);
    
        //注意要更新结点的高度。整个树中只有*T的左子树和lc的右子树发生了变化,所以只需更改这两棵树的高度。
        (*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
        lc->height = max(GetHeight(lc->lchild), GetHeight(lc->rchild)) + 1;
    
        *T = lc;
        return true;
    }
    
    /*
     * 当T的左子树的右子树上的节点使得T的平衡度为2时,
     * 先以T的左子树为中心进行左旋,再以T为中心进行右旋。
     */
    bool LRRotate(BiTree *T)
    {
        RRRotate(&((*T)->lchild));
        LLRotate(T);
        return true;
    }
    
    /*
     * 当T的右子树的左子树上的节点使得T的平衡度为-2时,
     * 先以T的右子树为中心进行右旋,再以T为中心进行左旋。
     */
    bool RLRotate(BiTree *T)
    {
        LLRotate(&((*T)->rchild));
        RRRotate(T);
        return true;
    }
    
    /*
     * 当T的右子树的右子树上的节点使得T的平衡度为-2时,以T为中心进行左旋。
     */
    bool RRRotate(BiTree *T)
    {
        BiTree rc;
        rc = (*T)->rchild;
        (*T)->rchild = rc->lchild;
        rc->lchild = (*T);
    
        //注意要更新结点的高度。整个树中只有*T的左子树和lc的右子树发生了变化,所以只需更改这两棵树的高度。
        (*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
        rc->height = max(GetHeight(rc->lchild), GetHeight(rc->rchild)) + 1;
    
        *T = rc;
        return true;
    }
    
    /*
     * 当T=NULL ,即树为空树时,无法通过T->height获取树的高度0,所以要额外编写该函数。
     */
    int GetHeight(BiTree T)
    {
        if (T)
            return T->height;
        return 0;
    }
    AVL.c
    /*
     * 二叉平衡树的插入和删除操作
     */
    #define _CRT_SECURE_NO_WARNINGS
    #include "AVL.h"
    
    #define TOTAL 10
    bool InOrderTraverse(BiTree T);
    bool print(BiTree *T);
    
    int main()
    {
        BiTree T = NULL;    //注意一定要将T初始化为空树。
        int i, num;
    
        //插入操作。
        printf("输入Total个数以构建二叉平衡树:
    ");
        for (i = 0; i < TOTAL; i++)
        {
            scanf("%d", &num);
            AVLInsert(&T, num);
        }
    
        //检验插入操作。
        printf("树结构为:
    ");
        print(&T);
        printf("中序遍历该树,检验是否为由小到大:
    ");
        InOrderTraverse(T);
        putchar('
    ');
        printf("树的深度为:%d
    ", T->height);
        
    
        //删除操作。
        printf("输入待删除的数:");
        scanf("%d", &num);
        AVLDelete(&T, num);
    
        //检验删除操作。
        printf("删除后,树结构为:
    ");
        print(&T);
        printf("中序遍历该树,检验是否为由小到大:
    ");
        InOrderTraverse(T);
        putchar('
    ');
        printf("树的深度为:%d
    ", T->height);
        return 0;
    }
    
    /*
     * 中序遍历输出树结构。
     */
    bool InOrderTraverse(BiTree T)
    {
        if (T)
        {
            InOrderTraverse(T->lchild);
            printf("%-3d ", T->data);
            InOrderTraverse(T->rchild);
        }
        return true;
    }
    
    /*
     * 输出树结构,且标明父子关系。
     */
    bool print(BiTree *T)
    {
        
        if (!*T)
            return false;
        else
        {
            //如果树有左孩子或者右孩子,则输出它的左孩子和右孩子。
            //例:5有左孩子3和右孩子6,输出形式为:5(3,6)
            //5只有左孩子3,输出形式为:5(3, )
            //5只有右孩子6,输出形式为:5( ,6)
            if ((*T)->lchild || (*T)->rchild)
            {
                printf("%d(", (*T)->data);
                if ((*T)->lchild)
                {
                    printf("%-3d,", (*T)->lchild->data);
                }
                else
                {
                    printf("   ,");
                }
                if ((*T)->rchild)
                {
                    printf("%-3d)
    ", (*T)->rchild->data);
                }
                else
                {
                    printf("   )
    ");
                }
            }
    
            //递归输出左孩子和右孩子。
            print(&((*T)->lchild));
            print(&((*T)->rchild));
            return true;
        }
    }
    main.c

    7.      测试结果

     无标题_2345看图王

    无标题2_2345看图王

    无标题3_2345看图王

     

     参考:

    【查找结构3】平衡查找二叉树[AVL]

    AVL树(一)之图文解析和C语言实现

     

     

  • 相关阅读:
    ckeditor添加自定义按钮整合swfupload实现批量上传图片
    H5移动端适配之px转vw(附工具)
    原生js实现复制文本到粘贴板
    快速删除项目中的输出日志console.log
    toString和valueOf使得对象访问时显示一个特定格式的字符串,但是可以进行数字运算
    __defineGetter__和__defineSetter__在日期中的应用
    观察者模式(订阅-发布者模式)
    原生js扫雷代码
    身份证验证思路及代码
    IMEI校验思路及代码
  • 原文地址:https://www.cnblogs.com/Camilo/p/3917041.html
Copyright © 2011-2022 走看看