数据结构3.3

 

 

 

 

 

 

讨论3.4 如何用堆栈实现后序遍历的非递归程序 

可以实现的,但不能通过简单的挪动一两个语句达到。因为树中每个二叉节点必须要将左子树、右子树遍历完才能print,而原程序在没有引入一个中间控制变量的情况下,对每一个被压入堆栈的节点只要做一次“移向左子树”或“移向右子树”就必然要被弹出,程序没有办法做到第一次访问该二叉节点压入堆栈,开始遍历其左子树;左子树遍历完成,然后第二次访问该节点,开始遍历右子树;右子树遍历完成,最后第三次访问该节点,才将该二叉节点弹出堆栈这么复杂的过程。 需要引入一个标志位—— 节点拥有的有效子树个数,并将该标志紧跟节点数据后压入堆栈,每次访问该节点时,先从堆栈弹出标志位,判断是否遍历节点的其他子树,还是print 输出该节点数据,如果还可以再做另一侧的子树遍历,则更新并重新压栈标志位。

另: 后序处理是左——右——中的顺序,而且,一个节点被访问三次。那么可以这样认为,第一次遍历,只压栈,不弹出,第二次访问的时候如果有左子树就继续遍历,没有就弹出左子树之后返回根节点,这是第二次遍历(这里要压栈),访问并弹出右子树节点后返回前一个父节点,这是第三次遍历。当然不能用调整原算法位置的方法实现,可以设计一个flag,手动规定flag为1或者2,为1时,说明已经访问完左子树,为2时访问完右子树。这时就可以弹出父节点了。


void PostorderTraversal( BinTree BT ){
    BinTree T = BT;
    Stack S = CreateStack();//创建堆栈
    while (T || !IsEmpty(S)){//当树不空或者堆栈不空时
        while (T){//一直访问左子树
            Push(S,T);//入栈
            T -> flag = 1;//记录第一次入栈
            T = T->Left;
        }
        if (!IsEmpty(S)){//左子树遍历完后
            T = Pop(S);//出栈
            if(T->flag == 1){//如果第一次入栈,右节点入栈
                Push(S,T);//入栈
                T->flag = 2;//意味两次入栈
                T = T->Right;//转为访问右子树
            }
           else{//如果第二次入栈
		        cout << " " << T->Data;//访问数据
                T = NULL;
            }
          
        }
    }
}

另:把后序遍历的顺序反过来,就是前序的思路,其中利用一个堆栈取反就行。

void PostOrderTraversal2(BinTree BT){
    BinTree T = BT;
    Stack S = CreateStack(); /*创建并初始化堆栈S*/
    Stack Q = CreateStack(); /*创建并初始化堆栈Q,用于输出反向*/
    while(T || !IsEmpty(S)){
        while(T){ /*一直向右并将沿途结点压入堆栈*/
            Push(T, S);
            Push(T, Q);/*将遍历到的结点压栈,用于反向*/
            T = T->Right;
        }
        if(!IsEmpty(S)){
            T = Pop(S); /*结点弹出堆栈*/
            T = T->Left; /*转向左子树*/
        }
    }
    while(!IsEmpty(Q)){
        T = Pop(Q);
        printf("%d ", T->Data); /*(访问)打印结点*/
    }
}

 

 

 

 

 

 

讨论3.5 将层序遍历中的队列改为堆栈

如果将层序遍历中的队列改为堆栈,是否也是一种树的遍历?可以应用这种方法改造出一种前序、中序、后序的非递归遍历吗?

层序遍历中的队列改为堆栈,也是一种遍历,遍历的结果为:A C I G H B F E D。

若按堆栈的方式操作层序遍历,并把子结点的压栈方式改为先压右子结点再压左子结点,则层序遍历的结果即为先序遍历;

若按照先压右子结点再压左子结点,并把按此规则遇到的子节点一一压入堆栈,至没有可压栈子结点后才开始逐一弹出堆栈,弹出堆栈的结点若还有子结点,则重新压入,并再按先压右子结点再压左子结点方式压栈…… 此方式得到的遍历,即为后序遍历;

从根结点开始,逐一将左子结点压栈,遇到无左子节点时,开始弹出堆栈,弹出的同时将该弹出结点的右结点再压栈,并继续按优先左结点压栈方式执行,遇到无左子节点时,开始弹出堆栈…… 此方式得到的遍历,即为中序遍历。

 无论什么遍历,只要加上if均可以输出叶节点。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

由先序序列和中序序列求后序


先序序列:gdafemhz
中序序列:adefghmz

第一步:先序序列的第一个字符是root,故root是g;

第二步:确定root后,看中序序列,可以看出,g左边的adef是左子树的结点,右边hmz是右子树的结点;

第三步:左子树adef的构成是怎么样的呢?这就看先序序列了,由概念可以看出,先序遍历先遍历根结点,所以如果该树有子树,那么先序序列的第二个字符就是root的左孩子;

第四步:左子树的中序序列的左子树的排列可以看出,a是d的左孩子,ef是d的右子树,根据中序遍历,先左再根再右的原则,可得f是d的右孩子,e是f的左孩子。

第五步:同理,可得m是g的右孩子,h是m的左孩子,z是m的右孩子。

故,该树可以画出


在这里插入图片描述
而选项D,分析一下,首先根据中序,A在中间,所以左子树的节点只有一个D,而右子树节点是B和C,但问题是根据前序遍历的规则,打印出A以后应该接着打印B,但是若D在左子树,B在右子树,则前序遍历不可能先打印出B再打印出D,所以这种情况不可能。 

 

后序的最后一个节点是A,则树根节点是A。

根据中序结果,A前面是左子树,即FDBE,后面是右子树CG

然后再对左子树进行剖析,B为左子树的根节点,左子树的左子子树是FD,D为根节点,因为中序遍历先打印了F,所以F为左子树。

又因为中序先打印了C再打印了G,所以G是右子树

树用图表示为:

前序遍历的结果为: ABDFECG
 

小结:二叉树的四种遍历

void InorderTraversal( BinTree BT )
{
    if( BT ) {
        InorderTraversal( BT->Left );
        /* 此处假设对BT结点的访问就是打印数据 */
        printf("%d ", BT->Data); /* 假设数据为整型 */
        InorderTraversal( BT->Right );
    }
}

void PreorderTraversal( BinTree BT )
{
    if( BT ) {
        printf("%d ", BT->Data );
        PreorderTraversal( BT->Left );
        PreorderTraversal( BT->Right );
    }
}

void PostorderTraversal( BinTree BT )
{
    if( BT ) {
        PostorderTraversal( BT->Left );
        PostorderTraversal( BT->Right );
        printf("%d ", BT->Data);
    }
}

void LevelorderTraversal ( BinTree BT )
{ 
    Queue Q; 
    BinTree T;

    if ( !BT ) return; /* 若是空树则直接返回 */
    
    Q = CreatQueue(); /* 创建空队列Q */
    AddQ( Q, BT );
    while ( !IsEmpty(Q) ) {
        T = DeleteQ( Q );
        printf("%d ", T->Data); /* 访问取出队列的结点 */
        if ( T->Left )   AddQ( Q, T->Left );
        if ( T->Right )  AddQ( Q, T->Right );
    }
}

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那就随便一点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值