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

来源:互联网 发布:eufi ubuntu 32位 编辑:程序博客网 时间:2024/05/16 11:33

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

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

前序的相关内容参见: 

链式二叉树的前序创建、递归前序遍历、非递归堆栈前序遍历、前序销毁以及求二叉树的深度

中序的相关内容参见:链式二叉树的中序创建、递归中序遍历、非递归堆栈中序遍历、中序销毁

1、数据结构

二叉树的一个结点的数据结构包含5个域:
(1)值域:保存结点的值,类型为ElemType,可以自己定义为合适的类型,为了简单,我们再次设定值域的类型为char
(2)指向父结点的指针
(3)指向左儿子结点的指针
(4)指向右儿子结点的指针
(5)利用堆栈实现后序遍历时需要该标志位来确定当前结点的左右子树是否都已经被访问(这个标志位在前序和中序时都用不到)
</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、递归创建后序二叉树

依然需要给出树的深度作为创建函数的参数。后创建时递归结束的条件是:树的深度为1。因此不管叶子结点是不是处在最底层,都要一直递归到最底层,而不在最底层的叶子结点的左右子树都为空,因此可以用输入若干个'.'来标志该结点为叶子结点。如果树的深度为depth,对于深度为i的叶子结点,在输入叶子结点的前要输入2depth– i + 1  -2 个'.',用以识别该结点为叶子结点。因为在创建过程中是左右根的思路,对于位于第i层的叶子结点,从最低层到第i层的距离为:depth-i,位于第i层的叶子结点的左子树为空,若将空结点也作为树的结点,则该左子树包含2depth – i -1个空结点,同样,其右子树也包含2depth – i -1个空结点,由于是左右根的顺序,故在输入该叶子结点前树要输入2depth – i + 1  -2 个空结点'.',当叶子结点位于最底层时,计算的该值为0,即最底层的叶子结点的前后不需要输入空结点'.'来标识。当一个深度为i的非叶子结点的左子树为空时,需要在输入该结点的右子树之前需要输入2depth – i -1个空结点'.',当其右子树为空时,需要在输入该结点之前输入2depth – i -1个空结点'.'。总之,需要输入的'.'的个数表示的含义是若该树为满二叉树时包含的结点的个数。因此,后序输入时,输入的字符的总个数为满二叉树的结点总个数:2depth - 1个,其中的空结点'.'最终都被释放,只留下非空结点。
比如输入:

e

╱  ╲

d          f

   ╱  ╲          ╲

    b           c           g

     ╱                     ╱   ╲

   a                        h          i

树的总深度为4:
a,h,i为最底层叶子结点,在输入a,h,i之前不需要输入空结点,
非叶子结点b的右子树为空,b的深度为3,在输入b之前应输入2– 3  -1 = 1个空结点,
叶子结点c的深度为3,则在输入c之前应输入2– 3 + 1  -2= 2个'.',
非叶子结点f的左子树为空,f的深度为2,在输入f的右子树之前应输入2– 2  -1 = 3个空结点,
(1)首先找到最左结点a,a位于最底层,在输入a之前不需要输入空结点,
(2)最左结点a的父结点b的右子树为空,非叶子结点b的深度为3,在输入b之前应输入2– 3  -1 = 1个空结点,然后输入b
(3)b的父结点d的右子树不为空,则应先输入d的右子树完毕后再输入d:
1)d的右子树只包含一个叶子结点c,c的深度为3,则应先输入2– 3 + 1  -2= 2个'.'标志该叶子结点,
2)然后输入c,此时d的左右子树均输入完毕,
3)最后输入d;

(4)d的父结点e的右子树不为空,则应先输入e的右子树完毕后再输入e:

1)e的右子树的根结点为f,f的左子树为空,f的深度为2,在输入f的右子树之前应输入2– 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)递归调用自身创建当前结点的右子树。

4)创建当前结点。

输入的字符的总个数为满二叉树的结点总个数:2depth - 1个,
BiTree *PostCreateBiTree( int depth )//按后序次序输入二叉树中结点的值(字符型),字符型的句号'.'表示为空树{BiTree *tree;if( depth == 0 ){   //二叉树深度为0,表示为空树,返回NULLtree = NULL;}else if( depth == 1 ){  //若二叉树深度为1,则其只包含一个结点,出现该种情况要么是深度为1的树的根结点,要么是深度不为1的树叶子节点tree = NodeMalloc(  );//为该结点分配空间printf( "请输入结点的值:\n" );scanf( "%s", &(tree->data) );//输入结点的值if( tree->data == '.' ){  //如果输入为字符型的'.',表示该结点为空,将为该结点分配的空间释放,并返回NULLfree( tree );tree = NULL;}}else{tree = NodeMalloc(  );//为当前树的根结点分配空间depth = depth - 1;  //以当前树的根结点的儿子结点为根结点的树的深度为当前树的深度减1tree->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 == '.' ){  //如果当前树的根结点为字符'.',则表示当前树为空树,将为该结点分配的空间释放,并返回NULLfree( tree );tree = NULL;}}return tree;}

3、递归的后序遍历

(1)树为空时,打印为空的信息
(2)当前结点为叶子结点时,访问当前结点
(3)当前结点不是叶子结点时:

1)若其左子树不为空,递归调用自身先按后序访问其左子树

2)若其右子树不为空,递归调用自身再按后序访问其右子树

3)访问当前结点

需要注意的是:由于当前结点不是叶子结点,所以1)和2)至少有一个会发生
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',表示正在对其左子树进行访问,然后将其入栈。此时栈顶存放的是树的最左结点,其左子树一定为空,右子树不一定为空。
(2)入栈完毕后,获得当前的栈顶结点,判断该结点的标志位flag:
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)若树不为空:判断树是否为叶子结点
1)若为叶子结点,则释放该结点
2)若不为叶子结点,将该结点的左右子树的根结点保留下来:
a.若左子树不为空,则递归调用函数自身销毁左子树,此时左子树不为空,所以递归调用后函数会回到(2)
b.若右子树不为空,则递归调用函数自身销毁右子树,此时右子树不为空,所以递归调用后函数会回到(2)
c.最后释放当前结点
需要注意的是:由于当前结点不是叶子结点,所以a和b至少有一个会发生。
//后序顺序销毁树是开销最小的,因为不需要额外的指针来保存左右子树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)若树为空,则返回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,表示树的深度为0return 0;else if( tree->leftchild == NULL && tree->rightchild == NULL ){  //如果树只有根结点,则树的深度为1return 1;}else{  //否则计算当前树的左右子树的深度depth_l = BiTreeDepth( tree->leftchild );depth_r = BiTreeDepth( tree->rightchild );if( depth_l > depth_r )  //当前树的深度为其左右子树深度较大的值加1return depth_l + 1;elsereturn depth_r + 1;}}

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

(1)头文件:bitree.h,树结点的数据结构比前序和中序多了一个标志位:flag
#define TRUE 1#define FALSE 0typedef 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 ); //弹栈,并将出栈元素的值赋给eint Length( Lstack *S );//栈中元素个数int StackEmpty( Lstack *S );//判断栈是否为空,若为空,返回TRUE,非空返回FALSEvoid 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:
详见链式二叉树的前序创建、递归前序遍历、非递归堆栈前序遍历、前序销毁以及求二叉树的深度。与其完全相同。
(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;   //初始化结点的各个指针域为NULLnode->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,表示为空树,返回NULLtree = NULL;}else if( depth == 1 ){  //若二叉树深度为1,则其只包含一个结点,出现该种情况要么是深度为1的树的根结点,要么是深度不为1的树叶子节点tree = NodeMalloc(  );//为该结点分配空间printf( "请输入结点的值:\n" );scanf( "%s", &(tree->data) );//输入结点的值if( tree->data == '.' ){  //如果输入为字符型的'.',表示该结点为空,将为该结点分配的空间释放,并返回NULLfree( tree );tree = NULL;}}else{tree = NodeMalloc(  );//为当前树的根结点分配空间depth = depth - 1;  //以当前树的根结点的儿子结点为根结点的树的深度为当前树的深度减1tree->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 == '.' ){  //如果当前树的根结点为字符'.',则表示当前树为空树,将为该结点分配的空间释放,并返回NULLfree( 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;elsereturn FALSE;}//判断树的深度int BiTreeDepth( BiTree *tree ) {int depth_r;int depth_l;if( tree == NULL )  //如果为空树,则返回0,表示树的深度为0return 0;else if( tree->leftchild == NULL && tree->rightchild == NULL ){  //如果树只有根结点,则树的深度为1return 1;}else{  //否则计算当前树的左右子树的深度depth_l = BiTreeDepth( tree->leftchild );depth_r = BiTreeDepth( tree->rightchild );if( depth_l > depth_r )  //当前树的深度为其左右子树深度较大的值加1return depth_l + 1;elsereturn 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

0 0
原创粉丝点击