讨论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 );
}
}