二叉樹幾種遍歷算法的非遞歸實現

二叉樹遍歷的非遞歸實現

相對於遞歸遍歷二叉樹,非遞歸遍歷顯得複雜了許多,但換來的好處是算法的時間效率有了提高。下面對於我學習非遞歸遍歷二叉樹算法的過程進行總結

爲了便於理解,這裏以下圖的二叉樹爲例,分析二叉樹的三種遍歷方式的實現過程。

一.非遞歸實現二叉樹的前序遍歷

不借助遞歸,要實現二叉樹的前序遍歷,我們需要用到前面學過的棧這種數據結構。根據前序遍歷的定義,先訪問根節點,再訪問左子樹,最後訪問右子樹。聲明指向節點的指針pCur,我們可以先訪問根節點,之後讓根節點進棧,並讓pCur在左子樹上移動直到pCur爲空時,令棧頂元素出棧,讓pCur在棧頂元素的右子樹上繼續移動即可

用自然語言描述爲:
對於任一節點P,
1)輸出節點P,然後將其入棧,再看P的左孩子是否爲空;
2)若P的左孩子不爲空,則置P的左孩子爲當前節點,重複1)的操作;
3)若P的左孩子爲空,則將棧頂節點出棧,但不輸出,並將出棧節點的右孩子置爲當前節點,看其是否爲空;
4)若不爲空,則循環至1)操作;
5)如果爲空,則繼續出棧,但不輸出,同時將出棧節點的右孩子置爲當前節點,看其是否爲空,重複4)和5)操作;
6)直到當前節點P爲NULL並且棧空,遍歷結束。

下面以上圖爲例詳細分析其先序遍歷的非遞歸實現過程:
首先,從根節點A開始,根據操作1),輸出A,並將其入棧,由於A的左孩子不爲空,根據操作2),將B置爲當前節點,再根據操作1),將B輸出,並將其入棧,由於B的左孩子也不爲空,根據操作2),將D置爲當前節點,再根據操作1),輸出D,並將其入棧,此時輸出序列爲ABD;
由於D的左孩子爲空,根據操作3),將棧頂節點D出棧,但不輸出,並將其右孩子置爲當前節點;
由於D的右孩子爲空,根據操作5),繼續將棧頂節點B出棧,但不輸出,並將其右孩子置爲當前節點;
由於B的右孩子E不爲空,根據操作1),輸出E,並將其入棧,此時輸出序列爲:ABDE;
由於E的左孩子爲空,根據操作3),將棧頂節點E出棧,但不輸出,並將其右孩子置爲當前節點;
由於E的右孩子爲空,根據操作5),繼續將棧頂節點A出棧,但不輸出,並將其右孩子置爲當前節點;
由於A的右孩子C不爲空,根據操作1),輸出C,並將其入棧,此時輸出序列爲:ABDEC;
由於A的左孩子F不爲空,根據操作2),則將F置爲當前節點,再根據操作1),輸出F,並將其入棧,此時輸出序列爲:ABDECF;
由於F的左孩子爲空,根據操作3),將棧頂節點F出棧,但不輸出,並將其右孩子置爲當前節點;
由於F的右孩子爲空,根據操作5),繼續將棧頂元素C出棧,但不輸出,並將其右孩子置爲當前節點;
此時棧空,且C的右孩子爲NULL,因此遍歷結束。

代碼如下:

Status NRPreorder(BiTree T){
BiTree stack[MAXSIZE],pCur=T;
int top=-1;
/*pCur不爲空或棧不爲空時循環*/
while(pCur||top>-1){
    Visit(pCur->data);/*打印當前節點*/
    top++;
    stack[top]=pCur;/*當前節點進棧*/
    pCur=pCur->Lchild;/*在左子樹上移動*/
    /*若左子樹爲空,則讓棧頂元素出棧,並在右子樹上尋找直到pCur不爲空*/
    while(!pCur&&top>-1){
        pCur=stack[top];
        top--;
        pCur=pCur->Rchild;
    }
}
return OK;

}

二.非遞歸實現中序遍歷

根據中序遍歷的定義,先訪問左子樹,再訪問根節點,最後訪問右子樹。那麼依然可以藉助棧實現遍歷。首先聲明指針pCur指向當前節點,若當前節點有左子樹,則當前節點入棧,若無左子樹,則打印當前節點,pCur指針進入右子樹,若無右子樹,則棧頂元素出棧,直到棧頂元素有右子樹爲止。

對於任一節點P,
1)若P的左孩子不爲空,則將P入棧並將P的左孩子置爲當前節點,然後再對當前節點進行相同的處理;
2)若P的左孩子爲空,則輸出P節點,而後將P的右孩子置爲當前節點,看其是否爲空;
3)若不爲空,則重複1)和2)的操作;
4)若爲空,則執行出棧操作,輸出棧頂節點,並將出棧的節點的右孩子置爲當前節點,看起是否爲空,重複3)和4)的操作;
5)直到當前節點P爲NULL並且棧爲空,則遍歷結束。

下面以上圖爲例詳細分析其中序遍歷的非遞歸實現過程:
首先,從根節點A開始,A的左孩子不爲空,根據操作1)將A入棧,接着將B置爲當前節點,B的左孩子也不爲空,根據操作1),將B也入棧,接着將D置爲當前節點,由於D的左子樹爲空,根據操作2),輸出D;
由於D的右孩子也爲空,根據操作4),執行出棧操作,將棧頂結點B出棧,並將B置爲當前節點,此時輸出序列爲DB;
由於B的右孩子不爲空,根據操作3),將其右孩子E置爲當前節點,由於E的左孩子爲空,根據操作1),輸出E,此時輸出序列爲DBE;
由於E的右孩子爲空,根據操作4),執行出棧操作,將棧頂節點A出棧,並將節點A置爲當前節點,此時輸出序列爲DBEA;
此時棧爲空,但當前節點A的右孩子並不爲NULL,繼續執行,由於A的右孩子不爲空,根據操作3),將其右孩子C置爲當前節點,由於C的左孩子不爲空,根據操作1),將C入棧,將其左孩子F置爲當前節點,由於F的左孩子爲空,根據操作2),輸出F,此時輸出序列爲:DBEAF;
由於F的右孩子也爲空,根據操作4),執行出棧操作,將棧頂元素C出棧,並將其置爲當前節點,此時的輸出序列爲:DBEAFC;
由於C的右孩子爲NULL,且此時棧空,根據操作5),遍歷結束。

代碼如下:

Status NRInorder(BiTree T){
BiTree stack[MAXSIZE],pCur=T;
int top=-1;
while(pCur||top>-1){
    if(pCur->Lchild){
        /*如果當前節點有左子樹,則入棧*/ 
        top++;
        stack[top]=pCur;
        pCur=pCur->Lchild;
    } 
    else{
        Visit(pCur->data);/*無左子樹,直接訪問當前節點*/
        pCur=pCur->Rchild;/*進入右子樹繼續訪問*/
        /*無右子樹,則棧頂元素出棧並打印*/
        while(!pCur&&top>-1){
            pCur=stack[top];
            top--;
            Visit(pCur->data);
            pCur=pCur->Rchild;
        }
    }
}

}

三.後序遍歷的非遞歸實現

根據後續遍歷的定義,先訪問其左子樹,再訪問其右子樹,最後訪問其根節點,依然藉助棧實現,聲明兩個指針pre,pCur。pCur用於指向當前節點,pre用於指向訪問過的節點。
先讓根節點入棧,當棧不空時,pCur指向棧頂,如果棧頂元素無左右子樹或左右子樹已經被輸出過,則直接打印當前節點,同時棧頂元素出棧,令pre=pCur。如果不是上述情況,則令當前節點的右子樹,左子樹依次入棧即可。

根據後序遍歷的順序,先訪問左子樹,再訪問右子樹,後訪問根節點,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的後序遍歷順序爲:DEBFCA。後序遍歷的非遞歸的實現相對來說要難一些,要保證根節點在左子樹和右子樹被訪問後才能訪問,思路如下:
對於任一節點P,
1)先將節點P入棧;
2)若P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已經被輸出,則可以直接輸出節點P,並將其出棧,將出棧節點P標記爲上一個輸出的節點,再將此時的棧頂結點設爲當前節點;
3)若不滿足2)中的條件,則將P的右孩子和左孩子依次入棧,當前節點重新置爲棧頂結點,之後重複操作2);
4)直到棧空,遍歷結束。

下面以上圖爲例詳細分析其後序遍歷的非遞歸實現過程:
首先,設置兩個指針:Cur指針指向當前訪問的節點,它一直指向棧頂節點,每次出棧一個節點後,將其重新置爲棧頂結點,Pre節點指向上一個訪問的節點;
Cur首先指向根節點A,Pre先設爲NULL,由於A存在左孩子和右孩子,根據操作3),先將右孩子C入棧,再將左孩子B入棧,Cur改爲指向棧頂結點B;
由於B的也有左孩子和右孩子,根據操作3),將E、D依次入棧,Cur改爲指向棧頂結點D;
由於D沒有左孩子,也沒有右孩子,根據操作2),直接輸出D,並將其出棧,將Pre指向D,Cur指向棧頂結點E,此時輸出序列爲:D;
由於E也沒有左右孩子,根據操作2),輸出E,並將其出棧,將Pre指向E,Cur指向棧頂結點B,此時輸出序列爲:DE;
由於B的左右孩子已經被輸出,即滿足條件Pre==Cur->lchild或Pre==Cur->rchild,根據操作2),輸出B,並將其出棧,將Pre指向B,Cur指向棧頂結點C,此時輸出序列爲:DEB;
由於C有左孩子,根據操作3),將其入棧,Cur指向棧頂節點F;
由於F沒有左右孩子,根據操作2),輸出F,並將其出棧,將Pre指向F,Cur指向棧頂結點C,此時輸出序列爲:DEBF;
由於C的左孩子已經被輸出,即滿足Pre==Cur->lchild,根據操作2),輸出C,並將其出棧,將Pre指向C,Cur指向棧頂結點A,此時輸出序列爲:DEBFC;
由於A的左右孩子已經被輸出,根據操作2),輸出A,並將其出棧,此時輸出序列爲:DEBFCA;
此時棧空,遍歷結束。

代碼如下:

Status NRPostorder(BiTree T){
BiTree stack[MAXSIZE],pCur=T,pre=NULL;/*pre用於標記已訪問過的節點*/
int top=-1;
stack[++top]=T;
while(top>-1){/*棧不空時循環*/
    pCur=stack[top];/*pCur始終指向棧頂*/
    /*如果當前節點沒有左右子樹或者左右子樹已經被訪問過*/
    if((!pCur->Lchild&&!pCur->Rchild)||(pre&&(pre==pCur->Lchild||pre==pCur->Rchild))){
        Visit(pCur->data);/*輸出當前節點的值*/
        top--;/*當前節點出棧*/
        pre=pCur;/*標記爲已訪問過的節點*/
    }
    /*不是上述情況,則當前節點左右子樹分別進棧*/
    else{
        if(pCur->Lchild)
           stack[++top]=pCur;
        if(pCur->Rchild)
           stack[++top]=pCur;
    }
}

}


四.二叉樹的層序遍歷


由於二叉樹具有層次結構,可以按照層序進行遍歷,考慮順序問題,我們採用隊列實現。從上到下從左到右依次入隊。算法思路大致是先讓根節點入隊,若根節點有左右子樹,則讓左右子樹依次入隊,之後讓隊首元素出隊並打印。反覆進行直到隊列空爲止

先將樹的根節點入隊,

如果隊列不空,則進入循環

{

將隊首元素出隊,並輸出它;

如果該隊首元素有左孩子,則將其左孩子入隊;

如果該隊首元素有右孩子,則將其右孩子入隊

代碼如下:

Status LevelOrderTraverse(BiTree T){
BiTree Queue[MAXSIZE],pCur=T;
int front=-1,tail=-1;
tail++;
Queue[tail]=pCur;/*根節點入隊*/
while(front!=tail){
    front++;
    Visit(Queue[front]->data);/*打印隊首節點*/
    if(Queue[front]->Lchild){
        tail++;
        Queue[tail]=Queue[front]->Lchild;/*有左子樹則左子樹入隊*/
    }
    if(Queue[front]->Rchild){
        tail++;
        Queue[tail]=Queue[front]->Rchild;
    }
}
printf("\n");

}

寫下這篇博文的過程中,參考了這兩篇博客:
http://blog.csdn.net/ns_code/article/details/13169703
http://blog.csdn.net/ns_code/article/details/12977901
向作者表示感謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章