链式二叉树的后序创建、递归后序遍历、非递归堆栈后序遍历、后序销毁
完整代码可到CSDN资源页搜索链式二叉树的后序创建、递归后序遍历、非递归堆栈后序遍历、后序销毁进行下载
前序的相关内容参见:
链式二叉树的前序创建、递归前序遍历、非递归堆栈前序遍历、前序销毁以及求二叉树的深度
中序的相关内容参见:链式二叉树的中序创建、递归中序遍历、非递归堆栈中序遍历、中序销毁
1、数据结构
</pre><pre name="code" class="cpp">typedef char ElemType;
typedef struct BiTree{
ElemType data;
struct BiTree *parent;
struct BiTree *leftchild;
struct BiTree *rightchild;
char flag;
}BiTree;
2、递归创建后序二叉树
e
╱ ╲
d f
╱ ╲ ╲
b c g
╱ ╱ ╲
a h i
(1)首先找到最左结点a,a位于最底层,在输入a之前不需要输入空结点,
1)d的右子树只包含一个叶子结点c,c的深度为3,则应先输入24 – 3 + 1 -2= 2个'.'标志该叶子结点,2)然后输入c,此时d的左右子树均输入完毕,3)最后输入d;
(4)d的父结点e的右子树不为空,则应先输入e的右子树完毕后再输入e:
1)e的右子树的根结点为f,f的左子树为空,f的深度为2,在输入f的右子树之前应输入24 – 2 -1 = 3个空结点,
2)f的右子树的根结点为g,g的左右子树都只包含一个位于最底层的叶子结点,故应输入h,i,g(左右根的顺序),
3)f的左右子树输入完毕,输入f,
4)e的左右子树输入完毕,输入e,结束。
结点的输入顺序为:a.b..cd...higfe
(1)第一个'.'表示位于第3层的非叶子结点b的右子树为空,
(2)第二个和第三个‘.'用于标志位于第3层的结点c为叶子结点,
(3)最后三个'.'用于表示位于第2层的非叶子结点f的左子树为空,
算法描述为:
(1)若depth为0,表示树为空。
(2)若depth为1,表示树只有一个根结点,创建该根结点。如果创建过程中输入的值域'.',表示该结点为空。
(3)若depth大于1,
1)先为当前的根结点分配空间,但不对其的值域进行创建。
2)递归调用自身创建当前结点的左子树。
3)递归调用自身创建当前结点的右子树。
输入的字符的总个数为满二叉树的结点总个数: 2 depth - 1个,
4)创建当前结点。
BiTree *PostCreateBiTree( 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 = PostCreateBiTree( depth ); //生成当前树的根结点的左子树
if( tree->leftchild ){ //如果左子树不为空
tree->leftchild->parent = tree; //左子树的根结点的父结点即为当前结点
}
tree->rightchild = PostCreateBiTree( depth ); //生成当前树的根结点的右子树
if( tree->rightchild ){ //如果右子树不为空
tree->rightchild->parent = tree;//右子树的根结点的父结点即为当前结点
}
printf( "请输入结点的值:\n" ); //获得当前根结点的值
scanf( "%s", &(tree->data) );
if( tree->data == '.' ){ //如果当前树的根结点为字符'.',则表示当前树为空树,将为该结点分配的空间释放,并返回NULL
free( tree );
tree = NULL;
}
}
return tree;
}
3、递归的后序遍历
(1)树为空时,打印为空的信息
(2)当前结点为叶子结点时,访问当前结点
(3)当前结点不是叶子结点时:
1)若其左子树不为空,递归调用自身先按后序访问其左子树
2)若其右子树不为空,递归调用自身再按后序访问其右子树
需要注意的是:由于当前结点不是叶子结点,所以1)和2)至少有一个会发生
3)访问当前结点
void PostOrderTraverse( 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 ){ //若当前结点的左子树不为空,则先按后序打印其左子树的结点
PostOrderTraverse( tree->leftchild, visit );
}
if( tree->rightchild ){ //如果当前树的根结点的右子树不为空,再按后序打印其右子树的结点
PostOrderTraverse( tree->rightchild, visit );
}
visit( tree ); //最后打印当前结点的值,
}
}
void PrintElem( BiTree *node )
{
if( node ){
printf( "%c", node->data );
}
}
4、利用栈实现非递归的后序遍历
1)若标志位为'L',表示其右子树还没有被访问,将该结点出栈,设置其标志位为'R',再将其压栈,并对其右子树进行访问。2)若标志位为'R',表示该结点的右子树已经访问完毕,将该结点出栈并对该结点进行访问。
//利用堆栈的非递归的后序遍历
void PostOrderTraverse_stack( BiTree *tree, void( *visit )( BiTree *node ))
{
BiTree *current;
Lstack *stack;
BiTree temp;
current = tree; //将current指向当前的树根结点
stack = InitStack( ); //将栈初始化为空栈
while( !StackEmpty( stack ) || current ){
while( current ){ //while循环的作用是将根结点至最左结点的所有左结点入栈
current->flag = 'L'; //先标记当前结点为L,表示正在进行其左子树的访问,再将其入栈
Push( stack, *current );
current = current->leftchild; //current指向其左儿子
}
while( !StackEmpty( stack ) ){ //如果栈非空(该while循环的作用是将所有右子树已经访问完毕的结点出栈并对其进行访问)
GetTop( stack, &temp ); //获得栈顶元素
if( temp.flag == 'R' ){ //如果栈顶元素的标志位为'R',表示其右子树已经访问完毕,因此可以弹出栈顶元素对其进行访问
Pop( stack, &temp ); //出栈并访问该结点
visit( &temp ); //此时对栈顶元素的访问相当于是对当前树的根结点的访问
}
else{
Pop( stack, &temp ); //出栈,设置标志位为'R',再入栈,目的是标明现在要对栈顶结点的右子树进行访问
temp.flag = 'R';
Push( stack, temp );
current = temp.rightchild; //将current置为当前栈顶结点的右子树的根结点
break; //结束该层循环进入外层循环对栈顶结点的右子树进行访问
}
}
}
}
5、后续销毁
1)若为叶子结点,则释放该结点2)若不为叶子结点,将该结点的左右子树的根结点保留下来:
需要注意的是:由于当前结点不是叶子结点,所以a和b至少有一个会发生。a.若左子树不为空,则递归调用函数自身销毁左子树,此时左子树不为空,所以递归调用后函数会回到(2)b.若右子树不为空,则递归调用函数自身销毁右子树,此时右子树不为空,所以 递归调用后函数会回到(2)c.最后 释放当前结点
//后序顺序销毁树是开销最小的,因为不需要额外的指针来保存左右子树
void PostDestroyBiTree( BiTree *tree ) //后序顺序销毁一个树,先释放左右子树,最后释放根结点(左右子树的释放顺序可以调换)
{
if( tree == NULL ){ //如果树为空树,则进行打印
printf( "DestroyBiTree:the tree is empty!\n" );
}
else if( tree->leftchild == NULL && tree->rightchild == NULL ){ //如果当前结点为叶子结点,则释放为其分配的空间
free( tree );
tree = NULL;
}
else{ //当一个树除根结点外还有其他结点时,要先释放根结点的左右子树,再释放根结点,否则先释放根结点后无法找到其左右子树了
//除非在释放之前先将左右子树的根结点信息保留下来,即用两个指针分别指向左右子树的根结点,这会增加开销
if( tree->leftchild ){ //如果当前结点的左子树不为空,则先释放左子树的各个结点
PostDestroyBiTree( tree->leftchild );
tree->leftchild = NULL; //左子树的空间释放完毕后,将指向左子树根结点的指针置为空
}
if( tree->rightchild ){//如果当前结点的右子树不为空,则再释放右子树的各个结点
PostDestroyBiTree( tree->rightchild );
tree->rightchild = NULL;//右子树的空间释放完毕后,将指向右子树根结点的指针置为空
}
PostDestroyBiTree( tree ); //当前结点的左右子树的空间都释放完毕后,再释放当前结点
}
}
6、判断树的深度
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 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;
}
}
7、为了完整性在此给出其他相关数据结构和函数
(1)头文件:bitree.h,树结点的数据结构比前序和中序多了一个标志位:flag#define TRUE 1
#define FALSE 0
typedef char ElemType;
typedef struct BiTree{
ElemType data;
struct BiTree *parent;
struct BiTree *leftchild;
struct BiTree *rightchild;
<span style="color:#ff0000;"><strong> char flag;</strong></span>
}BiTree;
typedef BiTree LElemType;
typedef struct temp{
struct temp *link;
LElemType data;
}Lstack;
Lstack * InitStack( );//初始化栈为空栈
void DestroyStack( Lstack *S );//将栈销毁
void ClearStack( Lstack *S ); //将栈清空
int GetTop( Lstack *S, LElemType *e );//获得栈顶元素,保持栈的状态不变
void Push( Lstack *S, LElemType e );//并将元素e压入栈顶
void Pop( Lstack *S, LElemType *e ); //弹栈,并将出栈元素的值赋给e
int Length( Lstack *S );//栈中元素个数
int StackEmpty( Lstack *S );//判断栈是否为空,若为空,返回TRUE,非空返回FALSE
void Print( Lstack *S ); //打印栈中的所有元素
BiTree *NodeMalloc( ); //为一个树结点分配空间
BiTree *InitBiTree( ); //构造空的二叉树
BiTree *PostCreateBiTree( int depth );//按后序次序输入二叉树中结点的值(字符型),字符型的句号'.'表示为空树
void PostDestroyBiTree( BiTree *tree ); //按后序次序销毁树
BiTree *ClearBiTree( BiTree *tree );//将二叉树清空
int BiTreeEmpty( BiTree *tree );//判断树是否为空
int BiTreeDepth( BiTree *tree );//判断树的深度
void PostOrderTraverse( BiTree *tree, void( *visit )( BiTree *node ));//递归后序遍历
void PrintElem( BiTree *node );//打印结点值
void PostOrderTraverse_stack( BiTree *tree, void( *visit )( BiTree *node ));//利用堆栈的非递归的后序遍历
(2)堆栈操作函数stack.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,对于不为于最底层的叶子结点,其前后都需要输入 (pow(2,depth-i+1) - pow(2,1))个'.'
//用以识别该结点为叶子结点,比如输入:a+b*(c-d)的后序,树的总深度为4,
//叶子结点a的深度为2,则在输入a之前应输入(pow(2,4-2+1) - pow(2,1)) = 6个'.',
//叶子结点b的深度为3,则在输入b之前应输入(pow(2,4-3+1) - pow(2,1)) = 2个'.'
//则后序输入的字符为:......a..bcd-*+由于为'.'分配的空间会被释放,得到的后序的结果最终为:abcd-*+
//后序输入时最终需要输入的结点个数 = ((pow(2,depth)-1)
BiTree *PostCreateBiTree( 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 = PostCreateBiTree( depth ); //生成当前树的根结点的左子树
if( tree->leftchild ){ //如果左子树不为空
tree->leftchild->parent = tree; //左子树的根结点的父结点即为当前结点
}
tree->rightchild = PostCreateBiTree( depth ); //生成当前树的根结点的右子树
if( tree->rightchild ){ //如果右子树不为空
tree->rightchild->parent = tree;//右子树的根结点的父结点即为当前结点
}
printf( "请输入结点的值:\n" ); //获得当前根结点的值
scanf( "%s", &(tree->data) );
if( tree->data == '.' ){ //如果当前树的根结点为字符'.',则表示当前树为空树,将为该结点分配的空间释放,并返回NULL
free( tree );
tree = NULL;
}
}
return tree;
}
//后序顺序销毁树是开销最小的,因为不需要额外的指针来保存左右子树
void PostDestroyBiTree( BiTree *tree ) //后序顺序销毁一个树,先释放左右子树,最后释放根结点(左右子树的释放顺序可以调换)
{
if( tree == NULL ){ //如果树为空树,则进行打印
printf( "DestroyBiTree:the tree is empty!\n" );
}
else if( tree->leftchild == NULL && tree->rightchild == NULL ){ //如果当前结点为叶子结点,则释放为其分配的空间
free( tree );
tree = NULL;
}
else{ //当一个树除根结点外还有其他结点时,要先释放根结点的左右子树,再释放根结点,否则先释放根结点后无法找到其左右子树了
//除非在释放之前先将左右子树的根结点信息保留下来,即用两个指针分别指向左右子树的根结点,这会增加开销
if( tree->leftchild ){ //如果当前结点的左子树不为空,则先释放左子树的各个结点
PostDestroyBiTree( tree->leftchild );
tree->leftchild = NULL; //左子树的空间释放完毕后,将指向左子树根结点的指针置为空
}
if( tree->rightchild ){//如果当前结点的右子树不为空,则再释放右子树的各个结点
PostDestroyBiTree( tree->rightchild );
tree->rightchild = NULL;//右子树的空间释放完毕后,将指向右子树根结点的指针置为空
}
PostDestroyBiTree( tree ); //当前结点的左右子树的空间都释放完毕后,再释放当前结点
}
}
void PostOrderTraverse( 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 ){ //若当前结点的左子树不为空,则先按后序打印其左子树的结点
PostOrderTraverse( tree->leftchild, visit );
}
if( tree->rightchild ){ //如果当前树的根结点的右子树不为空,再按后序打印其右子树的结点
PostOrderTraverse( tree->rightchild, visit );
}
visit( tree ); //最后打印当前结点的值,
}
}
void PrintElem( BiTree *node )
{
if( node ){
printf( "%c", node->data );
}
}
//利用堆栈的非递归的后序遍历
void PostOrderTraverse_stack( BiTree *tree, void( *visit )( BiTree *node ))
{
BiTree *current;
Lstack *stack;
BiTree temp;
current = tree; //将current指向当前的树根结点
stack = InitStack( ); //将栈初始化为空栈
while( !StackEmpty( stack ) || current ){
while( current ){ //while循环的作用是将根结点至最左结点的所有左结点入栈
current->flag = 'L'; //先标记当前结点为L,表示正在进行其左子树的访问,再将其入栈
Push( stack, *current );
current = current->leftchild; //current指向其左儿子
}
while( !StackEmpty( stack ) ){ //如果栈非空(该while循环的作用是将所有右子树已经访问完毕的结点出栈并对其进行访问)
GetTop( stack, &temp ); //获得栈顶元素
if( temp.flag == 'R' ){ //如果栈顶元素的标志位为'R',表示其右子树已经访问完毕,因此可以弹出栈顶元素对其进行访问
Pop( stack, &temp ); //出栈并访问该结点
visit( &temp ); //此时对栈顶元素的访问相当于是对当前树的根结点的访问
}
else{
Pop( stack, &temp ); //出栈,设置标志位为'R',再入栈,目的是标明现在要对栈顶结点的右子树进行访问
temp.flag = 'R';
Push( stack, temp );
current = temp.rightchild; //将current置为当前栈顶结点的右子树的根结点
break; //结束该层循环进入外层循环对栈顶结点的右子树进行访问
}
}
}
}
//将二叉树清空,在此不用对tree进行是否非空的判断,因为在PostDestroyBiTree函数中已经进行了判断
BiTree *ClearBiTree( BiTree *tree )
{
PostDestroyBiTree( tree );
return NULL;
}
//判断树是否为空
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;
}
}
(4)主函数main.c
#include< stdio.h >
#include"bitree.h"
void main()
{
BiTree *bi;
int depth;
printf( "please input th depth of the tree:\n" );
scanf( "%d", &depth );
printf( "后序顺序构建二叉树,对于树的深度为depth,当非最底层的叶子结点的深度为i时\n" );
printf( "在叶子结点前后都要输入(pow(2,depth-i+1) - pow(2,1))个'.'以标志该结点为叶子结点\n" );
printf( "*******PostCreateBiTree*******\n" );
bi = PostCreateBiTree( depth );
printf( "*******PostOrderTraverse******\n" );
PostOrderTraverse( bi, PrintElem );
printf( "\n" );
printf( "***PostOrderTraverse_stack****\n" );
PostOrderTraverse_stack( bi, PrintElem );
printf( "\n" );
printf( "*********BiTreeDepth**********\n" );
depth = BiTreeDepth( bi );
printf( "the depth of the tree is %d\n", depth );
printf( "******PostDestroyBiTree*******\n" );
PostDestroyBiTree( bi );
}
e
╱ ╲
d f
╱ ╲ ╲
b c g
╱ ╱ ╲
a h i