链式二叉树的中序创建、递归中序遍历、非递归堆栈中序遍历、中序销毁

链式二叉树的中序创建、递归中序遍历、非递归堆栈中序遍历、中序销毁

完整代码可到CSDN资源页搜索链式二叉树的中序创建、递归中序遍历、非递归堆栈中序遍历、中序销毁进行下载。

1、数据结构

二叉树的一个结点的数据结构包含4个域:
(1)值域:保存结点的值,类型为ElemType,可以自己定义为合适的类型,为了简单,我们再次设定值域的类型为char
(2)指向父结点的指针
(3)指向左儿子结点的指针
(4)指向右儿子结点的指针
typedef char ElemType;  
typedef struct BiTree{  
    ElemType data;  
    struct BiTree *parent;  
    struct BiTree *leftchild;  
    struct BiTree *rightchild;  
}BiTree; 

2、递归创建中序二叉树

中序二叉树的创建顺序即为先创建树的左子树,再创建树的根结点,最后创建树的右子树,即为左右的顺序。

对于前序创建二叉树(详见链式二叉树的前序创建、递归前序遍历、非递归堆栈前序遍历、前序销毁以及求二叉树的深度),对于叶子结点可以用在输入叶子结点后连续输入两个'.'来标记,而在中序创建时,最先创建的是最左结点,最左结点的左子树肯定为空,但其右子树不一定为空,即最左结点不一定是叶子结点。为了中序创建二叉树,必须先给出二叉树的深度最为创建函数的参数(在前序创建时,是不需要给出深度的,想了好久,也没想出没有给出深度时怎么进行中序创建)。中序创建时递归结束的条件是:树的深度为1。因此不管叶子结点是不是处在最底层,都要一直递归到最底层,而不在最底层的叶子结点的左右子树都为空。如果树的深度为depth,对于深度为i的叶子结点,在叶子结点的前后都要输入(depth-i)个'.',用以识别该结点为叶子结点。对于左子树为空的非叶子结点,在输入该结点前输入输入(depth-i)个'.'来标识其左子树为空,对于右子树为空的非叶子结点,在输入该结点后要输入(depth-i)个'.'来标识该其右子树为空。

比如输入:

e

╱  ╲

d          f

   ╱  ╲          ╲

    b           c           g

     ╱                     ╱   ╲

   a                        h          i

树的深度为4:

叶子结点a,h,i的深度为4,则在输入a之前和之后都要 输入(4-4) = 0个'.',即最底层叶子结点的前后不需要输入空结点来标识,

叶子结点c的深度为3,则在输入c之前和之后应输入(4-3) = 1个'.'来标识其为叶子结点

非叶子结点b的深度为3,其右子树为空,则在输入b之后要输入4-3=1个'.'来标志其右子树为空,

非叶子结点f的深度为2,其左子树为空,则在输入f之前要输入4-2=2个'.'来标志其左子树为空。

(1)输入最左结点a,

(2)输入最左结点a的父结点b,

(3)输入b的右子树,b的右子树为空,b的深度为3,在输入b之后要输入4-3=1个'.'来标志其右子树为空,

(4)输入b的父结点d,

(5)输入d的右子树,d的右子树只包含一个叶子结点c,叶子结点c的深度为3,则在输入c之前和之后应输入(4-3) = 1个'.'来标识其为叶子结点,

(6)输入d的父结点e,

(7)输入e的右子树,e的右子树的根结点f的左子树为空,非叶子结点f的深度为2,则在输入f之前要输入4-2=2个'.'来标志其左子树为空,

(8)输入f的右子树,f的右子树的根结点g的左右子树都不为空,且都只包含一个位于树的最底层的叶子结点,

1)输入g的左儿子结点h

2)输入g

3)输入g的右儿子结点i

最终输入的结点顺序为:ab.d.c.e..fhgi

(1)第一个'.'表示b的右子树为空

(2)第二个'.'和第三个'.'表示c为叶子结点

(3)第四个和第五'.'表示f的左子树为空

算法描述为:

(1)若depth为0,表示树为空。

(2)若depth为1,表示树只有一个根结点,创建该根结点。如果创建过程中输入的值域'.',表示该结点为空。

(3)若depth大于1,

1)先为当前的根结点分配空间,但不对其的值域进行创建。

2)递归调用自身创建当前结点的左子树。

3)创建当前结点。

4)当前结点不为空时递归调用自身创建其右子树。

最终需要输入的结点个数 = 

总结点个数 ++

<span style="font-size: 14px; font-weight: normal;">BiTree *InOrderCreateBiTree( int depth ) //按中序次序输入二叉树中结点的值(一个字符),空格字符表示空树  
{  
  
    BiTree *tree;  
      
    if( depth == 0 ){   //二叉树深度为0,表示为空树,返回NULL  
        tree = NULL;  
    }  
    else if( depth == 1 ){  //若二叉树深度为1,则其只包含一个结点,出现该种情况要么是深度为1的树的根结点,要么是深度不为1的树叶子节点  
        tree = NodeMalloc(  );//为该结点分配空间  
        printf( "请输入结点的值:\n" );  
        scanf( "%s", &(tree->data) );//输入结点的值  
        if( tree->data == '.' ){  //如果输入为字符型的'.',表示该结点为空,将为该结点分配的空间释放,并返回NULL  
            free( tree );  
            tree = NULL;  
        }  
    }  
    else{  
        tree = NodeMalloc(  );//为当前树的根结点分配空间  
        depth = depth - 1;  //以当前树的根结点的儿子结点为根结点的树的深度为当前树的深度减1  
          
        tree->leftchild = InOrderCreateBiTree( depth ); //生成当前树的根结点的左子树  
        if( tree->leftchild ){  //如果左子树不为空  
            tree->leftchild->parent = tree;  //左子树的根结点的父结点即为当前结点  
        }  
        printf( "请输入结点的值:\n" );  //获得当前根结点的值  
        scanf( "%s", &(tree->data) );  
        if( tree->data == '.' ){  //如果当前树的根结点为字符'.',则表示当前树为空树,将为该结点分配的空间释放,并返回NULL  
            free( tree );  
            tree = NULL;  
        }  
        if( tree ){  //若当前树不为空树,则生成当前树的根结点的右子树  
            tree->rightchild = InOrderCreateBiTree( depth );    
            if( tree->rightchild ){   //如果右子树不为空  
                tree->rightchild->parent = tree;//右子树的根结点的父结点即为当前结点  
            }  
        }  
    }  
    return tree;  
}</span>

3、递归的中序遍历

(1)树为空时,打印为空的信息
(2)当前结点为叶子结点时,打印当前结点
(3)当前结点不是叶子结点时:
1)若其左子树不为空,先按中序访问其左子树
2)访问当前结点
3)若其右子树不为空,按中序访问其右子树
void InOrderTraverse( BiTree *tree, void( *visit )( BiTree *node ) ) //按中序次序输出二叉树中各个结点的值(字符型)
{
	if( tree == NULL )
		printf( "the tree is empty!\n" );
	else if( tree->leftchild == NULL && tree->rightchild == NULL ){ //当前结点为叶子结点则输出当前结点的值	
		visit( tree );  
	}
	else{  //当前结点不是叶子结点
		if( tree->leftchild ){               //若当前结点的左子树不为空,则再按中序访问其左子树的结点
			InOrderTraverse( tree->leftchild, visit );  
		}
		visit( tree );   //再先打印当前结点的值

		if( tree->rightchild ){      //最后如果当前树的根结点的右子树不为空,再按中序打印其右子树的结点
			InOrderTraverse( tree->rightchild, visit );
		}
	}
}

4、利用栈实现非递归的中序遍历

首先将从根结点开始一直到最左结点的所有结点入栈。栈顶保存的即为当前未被访问的最左结点,将该结点出栈,对其进行访问。判断该结点的右子树是否为空,若为空,继续弹栈;若不为空,将对其右子树进行访问。

void InOrderTraverse_stack( BiTree *tree, void( *visit )( BiTree *node ))
{
	BiTree *current;
	Lstack *stack;
	BiTree left;

	current = tree;   //将current指向当前的树根结点
	stack = InitStack( );  //将栈初始化为空栈
	while( !StackEmpty( stack ) || current ){ //该while循环的作用是将从树根开始的至最左结点压入栈中,栈顶为当前未被访问过的最左结点										
		if( current ){   //如果current结点不为空
			Push( stack, *current );   //将current结点压栈
			current = current->leftchild;  //current指向其左儿子
		} 
		else{
			if( StackEmpty( stack ) )
				break;
			Pop( stack, &left); //栈顶为当前未被访问过的最左结点,将该最左结点出栈
			visit( &left );      //访问该最左结点
			current = left.rightchild;	 //遍历该最左结点的右子树
	//如果右子树为空,则应继续弹栈继续访问当前未被访问过的最左结点,此时current仍未NULL;因此再次进入循环时会直接进入else语句中弹栈访问
		}
	} //当栈为空且current为空时当前树的所有结点都被访问过,仅有栈为空时表示当前树的左子树的所有结点都遍历完了
	DestroyStack( stack );
}

5、中序销毁

(1)若树为空,打印树为空的信息
(2)若树不为空:判断树是否为叶子结点
1)若为叶子结点,则释放该结点
2)若不为叶子结点,将该结点的左右子树的根结点保留下来:
a.若左子树不为空,则递归调用函数自身销毁左子树,此时左子树不为空,所以进入调用函数后会回到(2)
b.左子树释放完毕后,释放当前结点
c.若右子树不为空,则递归调用函数自身销毁右子树,此时右子树不为空,所以进入调用函数后会回到(2)
需要注意的是:由于当前释放的结点不是叶子结点,所以a和c至少有一个会发生。

void InOrderDestroyBiTree( BiTree *tree )  //按中序顺序销毁一个树,即先释放左子树,再释放根结点,最后释放右子树(或者左右子树的释放顺序可以调换)
{
	BiTree *left;
	BiTree *right;

	if( tree == NULL ){  //如果树为空树,则进行打印
		printf( "DestroyBiTree:the tree is empty!\n" );
	}
	else if( tree->leftchild == NULL && tree->rightchild == NULL ){  //如果当前结点为叶子结点,则释放为其分配的空间
		free( tree );
		tree = NULL;
	}
	else{  
		left = tree->leftchild;
		right = tree->rightchild;
		
		if( left ){    //如果当前结点的左子树不为空,则先释放左子树的各个结点
			InOrderDestroyBiTree( left );
			left = NULL;  //左子树的空间释放完毕后,将指向左子树根结点的指针置为空
		}

	
		free( tree );  //当前结点的左子树的空间都释放完毕后,再释放当前结点
		tree = NULL;

		if( right ){//如果当前结点的右子树不为空,则再释放右子树的各个结点
			InOrderDestroyBiTree( right );
			right = NULL;//右子树的空间释放完毕后,将指向右子树根结点的指针置为空
		}
	}
}

6、判断树的深度

利用递归进行判断:
(1)若树为空,则返回0
(2)若树不为空:则树的深度为其左右子树中深度值较大的值加1

1)计算左子树的深度depth_l,递归调用相当于又从(1)开始

2)计算右子树的深度depth_r,递归调用相当于又从(1)开始

3)比较两个深度值的大小,返回较大值加1为当前树的深度

//判断树的深度  
int BiTreeDepth( BiTree *tree )   
{  
    int depth_r;  
    int depth_l;  
    if( tree == NULL )  //如果为空树,则返回0,表示树的深度为0  
        return 0;  
    else{  //否则计算当前树的左右子树的深度  
        depth_l = BiTreeDepth( tree->leftchild );  
        depth_r = BiTreeDepth( tree->rightchild );  
          
        if( depth_l > depth_r )  //当前树的深度为其左右子树深度较大的值加1  
            return depth_l + 1;  
        else  
            return depth_r + 1;  
    }  
}  

7、为了完整性在此给出其他相关数据结构和函数

(1)头文件bitree.h:

(2)堆栈函数stack.c:

详见链式二叉树的前序创建、递归前序遍历、非递归堆栈前序遍历、前序销毁以及求二叉树的深度。与其完全相同。

(3)树函数bitree.c

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

BiTree *InitBiTree(  )  //构造空的二叉树
{
	 return NULL;
}

BiTree * NodeMalloc(  )  //为一个树结点分配空间并将结点进行初始化
{
	BiTree *node;
	node = ( BiTree * )malloc( sizeof( BiTree ) );
	if( node == NULL ){
		printf( "NodeMalloc:OVERFLOW\n" );
		exit( EXIT_FAILURE );
	}
	node->leftchild = NULL;   //初始化结点的各个指针域为NULL
	node->rightchild = NULL;
	node->parent = NULL;
	return node;
}

//按中序输入时对于不处在最底层的叶子结点,假设树的深度为depth,叶子结点深度为i,在叶子结点的前后都要输入(depth-i)个'.',
//用以识别该结点为叶子结点.比如输入:a+b*(c-d)的中序,a的和b时不处在最底层的叶子结点
//叶子结点a的深度为2,则在输入a之前和之后都要 输入(4-2) = 2个'.',
//叶子结点b的深度为3,则在输入b之前和之后应输入(4-3) = 1个'.',
//则应输入..a..+.b.*c-d因为为字符'.'分配的空间会被释放,最终得到的结果会是a+b*c-d
//最终需要输入的结点个数 = 总结点个数 + (位于第i(i<depth)的叶子结点的个数*(depth-i)*2)求和
BiTree *InOrderCreateBiTree( int depth ) //按中序次序输入二叉树中结点的值(一个字符),空格字符表示空树
{

	BiTree *tree;
	
	if( depth == 0 ){   //二叉树深度为0,表示为空树,返回NULL
		tree = NULL;
	}
	else if( depth == 1 ){  //若二叉树深度为1,则其只包含一个结点,出现该种情况要么是深度为1的树的根结点,要么是深度不为1的树叶子节点
		tree = NodeMalloc(  );//为该结点分配空间
		printf( "请输入结点的值:\n" );
		scanf( "%s", &(tree->data) );//输入结点的值
		if( tree->data == '.' ){  //如果输入为字符型的'.',表示该结点为空,将为该结点分配的空间释放,并返回NULL
			free( tree );
			tree = NULL;
		}
	}
	else{
		tree = NodeMalloc(  );//为当前树的根结点分配空间
		depth = depth - 1;  //以当前树的根结点的儿子结点为根结点的树的深度为当前树的深度减1
		
		tree->leftchild = InOrderCreateBiTree( depth ); //生成当前树的根结点的左子树
		if( tree->leftchild ){  //如果左子树不为空
			tree->leftchild->parent = tree;  //左子树的根结点的父结点即为当前结点
		}
		printf( "请输入结点的值:\n" );  //获得当前根结点的值
		scanf( "%s", &(tree->data) );
		if( tree->data == '.' ){  //如果当前树的根结点为字符'.',则表示当前树为空树,将为该结点分配的空间释放,并返回NULL
			free( tree );
			tree = NULL;
		}
		if( tree ){  //若当前树不为空树,则生成当前树的根结点的右子树
			tree->rightchild = InOrderCreateBiTree( depth );  
			if( tree->rightchild ){   //如果右子树不为空
				tree->rightchild->parent = tree;//右子树的根结点的父结点即为当前结点
			}
		}
	}
	return tree;
}

void InOrderDestroyBiTree( BiTree *tree )  //按中序顺序销毁一个树,即先释放左子树,再释放根结点,最后释放右子树(或者左右子树的释放顺序可以调换)
{
	BiTree *left;
	BiTree *right;

	if( tree == NULL ){  //如果树为空树,则进行打印
		printf( "DestroyBiTree:the tree is empty!\n" );
	}
	else if( tree->leftchild == NULL && tree->rightchild == NULL ){  //如果当前结点为叶子结点,则释放为其分配的空间
		free( tree );
		tree = NULL;
	}
	else{  
		left = tree->leftchild;
		right = tree->rightchild;
		
		if( left ){    //如果当前结点的左子树不为空,则先释放左子树的各个结点
			InOrderDestroyBiTree( left );
			left = NULL;  //左子树的空间释放完毕后,将指向左子树根结点的指针置为空
		}

		tree->leftchild = NULL;
		tree->rightchild = NULL;
		InOrderDestroyBiTree( tree );  //当前结点的左子树的空间都释放完毕后,再释放当前结点
		tree = NULL;

		if( right ){//如果当前结点的右子树不为空,则再释放右子树的各个结点
			InOrderDestroyBiTree( right );
			right = NULL;//右子树的空间释放完毕后,将指向右子树根结点的指针置为空
		}
	}
}


void InOrderTraverse( BiTree *tree, void( *visit )( BiTree *node ) ) //按中序次序输出二叉树中各个结点的值(字符型)
{
	if( tree == NULL )
		printf( "the tree is empty!\n" );
	else if( tree->leftchild == NULL && tree->rightchild == NULL ){ //当前结点为叶子结点则输出当前结点的值	
		visit( tree );  
	}
	else{  //当前结点不是叶子结点
		if( tree->leftchild ){               //若当前结点的左子树不为空,则再按中序访问其左子树的结点
			InOrderTraverse( tree->leftchild, visit );  
		}
		visit( tree );   //再先打印当前结点的值

		if( tree->rightchild ){      //最后如果当前树的根结点的右子树不为空,再按中序打印其右子树的结点
			InOrderTraverse( tree->rightchild, visit );
		}
	}
}
	

//判断树是否为空
int BiTreeEmpty( BiTree *tree )
{
	if( tree == NULL )
		return TRUE;
	else
		return FALSE;
}

//判断树的深度
int BiTreeDepth( BiTree *tree ) 
{
	int depth_r;
	int depth_l;
	if( tree == NULL )  //如果为空树,则返回0,表示树的深度为0
		return 0;
	else if( tree->leftchild == NULL && tree->rightchild == NULL ){  //如果树只有根结点,则树的深度为1
		return 1;
	}
	else{  //否则计算当前树的左右子树的深度
		depth_l = BiTreeDepth( tree->leftchild );
		depth_r = BiTreeDepth( tree->rightchild );
		if( depth_l > depth_r )  //当前树的深度为其左右子树深度较大的值加1
			return depth_l + 1;
		else
			return depth_r + 1;
	}
}


void PrintElem( BiTree *node )
{
	if( node ){
		printf( "%c", node->data );
	}
}



//利用堆栈的非递归的中序遍历
void InOrderTraverse_stack( BiTree *tree, void( *visit )( BiTree *node ))
{
	BiTree *current;
	Lstack *stack;
	BiTree left;

	current = tree;   //将current指向当前的树根结点
	stack = InitStack( );  //将栈初始化为空栈
	while( !StackEmpty( stack ) || current ){	 //该while循环的作用是将从树根开始的至最左结点压入栈中,栈顶为当前未被访问过的最左结点										
		if( current ){   //如果current结点的左儿子不为空
			Push( stack, *current );   //将current结点压栈
			current = current->leftchild;  //current指向其左儿子
		} 
		else{
			if( StackEmpty( stack ) )
				break;
			Pop( stack, &left); //栈顶为当前未被访问过的最左结点,将该最左结点出栈
			visit( &left );      //访问该最左结点
			if( left.rightchild )       //如果最左结点的右子树不为空
				current = left.rightchild;	 //遍历该最左结点的右子树
	//如果右子树为空,则应继续弹栈继续访问当前未被访问过的最左结点,此时current仍未NULL;因此再次进入循环时会直接进入else语句中弹栈访问
		}
	} //当栈为空且current为空时当前树的所有结点都被访问过,仅有栈为空时表示当前树的左子树的所有结点都遍历完了
	DestroyStack( stack );
}

(4)主函数main.c

<span style="font-weight: normal;">#include< stdio.h >
#include"bitree.h"
void main()
{
	BiTree *bi;
	int depth;

	printf( "please input the depth of the tree:\n" );
	scanf( "%d", &depth );
	printf( "中序顺序构建二叉树,对于树的深度为depth,当非最底层的叶子结点的深度为i时\n" );
	printf( "在叶子结点前后都要输入(depth-i)个'.'以标志该结点为叶子结点\n" );

	printf( "*****InOrderCreateBiTree*****\n" );
	bi = InOrderCreateBiTree( depth ); 
	
	printf( "*******InOrderTraverse*******\n" );
	InOrderTraverse( bi, PrintElem );
	printf( "\n" );

	printf( "****InOrderTraverse_stack****\n" );
	InOrderTraverse_stack( bi, PrintElem );
	printf( "\n" );
	
	printf( "*********BiTreeDepth*********\n" );
	depth = BiTreeDepth( bi );
	printf( "the depth of the tree is %d\n", depth );
	

	printf( "****InOrderDestroyBiTree*****\n" );
	InOrderDestroyBiTree( bi );

}</span>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值