1. 程式人生 > >二叉樹的遞迴遍歷和迴圈遍歷

二叉樹的遞迴遍歷和迴圈遍歷

二叉樹是一種非常重要的資料結構,很多其他資料機構都是基於二叉樹的基礎演變過來的。二叉樹有前、中、後三種遍歷方式,因為樹的本身就是用遞迴定義的,因此採用遞迴的方法實現三種遍歷,不僅程式碼簡潔且容易理解,但其開銷也比較大,而若採用非遞迴方法實現三種遍歷,則要用棧來模擬實現(遞迴也是用棧實現的)。下面先簡要介紹三種遍歷方式的遞迴實現,再詳細介紹三種遍歷方式的非遞迴實現。

一、三種遍歷方式的遞迴實現(比較簡單,這裡不詳細講解)

1、先序遍歷——按照“根節點-左孩子-右孩子”的順序進行訪問。

  1. void pre_traverse(BTree pTree)  
  2. {  
  3.     if
    (pTree)  
  4.     {  
  5.         printf(”%c ”,pTree->data);  
  6.         if(pTree->pLchild)  
  7.             pre_traverse(pTree->pLchild);  
  8.         if(pTree->pRchild)  
  9.             pre_traverse(pTree->pRchild);      
  10.     }  
  11. }  

    2、中序遍歷——按照“左孩子-根節點-右孩子”的順序進行訪問。

  1. void in_traverse(BTree pTree)  
  2. {  
  3.     if(pTree)  
  4.     {  
  5.         if(pTree->pLchild)  
  6.             in_traverse(pTree->pLchild);  
  7.         printf(”%c ”,pTree->data);  
  8.         if(pTree->pRchild)  
  9.             in_traverse(pTree->pRchild);   
  10.     }  
  11. }  

    3、後序遍歷——按照“左孩子-右孩子-根節點”的順序進行訪問。

  1. void beh_traverse(BTree pTree)  
  2. {  
  3.     if(pTree)  
  4.     {  
  5.         if(pTree->pLchild)  
  6.             beh_traverse(pTree->pLchild);  
  7.         if(pTree->pRchild)  
  8.             beh_traverse(pTree->pRchild);      
  9.         printf(”%c ”,pTree->data);  
  10. }  

    二、三種遍歷方式的非遞迴實現

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


          1、前序遍歷的非遞迴實現 

根據先序遍歷的順序,先訪問根節點,再訪問左子樹,後訪問右子樹,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的先序遍歷順序為:ABDECF。非遞迴的實現思路如下:

對於任一節點P

1)輸出節點P,然後將其入棧,再看P的左孩子是否為空;

2)P的左孩子不為空,則置P的左孩子為當前節點,重複1)的操作;

3)P的左孩子為空,則將棧頂節點出棧,但不輸出,並將出棧節點的右孩子置為當前節點,看其是否為空;

4)若不為空,則迴圈至1)操作;

5)如果為空,則繼續出棧,但不輸出,同時將出棧節點的右孩子置為當前節點,看其是否為空,重複4)和5)操作;

6)直到當前節點PNULL並且棧空,遍歷結束。

   

   下面以上圖為例詳細分析其先序遍歷的非遞迴實現過程:

首先,從根節點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,因此遍歷結束。

   根據以上思路,前序遍歷的非遞迴實現程式碼如下:

  1. void pre_traverse(BTree pTree)  
  2. {  
  3.     PSTACK stack = create_stack();  //建立一個空棧
  4.     BTree node_pop;                 //用來儲存出棧節點
  5.     BTree pCur = pTree;             //定義用來指向當前訪問的節點的指標
  6.     //直到當前節點pCur為NULL且棧空時,迴圈結束
  7.     while(pCur || !is_empty(stack))  
  8.     {  
  9.         //從根節點開始,輸出當前節點,並將其入棧,
  10.         //同時置其左孩子為當前節點,直至其沒有左孩子,及當前節點為NULL
  11.         printf(”%c ”, pCur->data);  
  12.         push_stack(stack,pCur);  
  13.         pCur = pCur->pLchild;  
  14.         //如果當前節點pCur為NULL且棧不空,則將棧頂節點出棧,
  15.         //同時置其右孩子為當前節點,迴圈判斷,直至pCur不為空
  16.         while(!pCur && !is_empty(stack))  
  17.         {  
  18.             pCur = getTop(stack);  
  19.             pop_stack(stack,&node_pop);  
  20.             pCur = pCur->pRchild;              
  21.         }  
  22.     }  
  23. }  

   2、中序遍歷的非遞迴實現

根據中序遍歷的順序,先訪問左子樹,再訪問根節點,後訪問右子樹,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的中序遍歷順序為:DBEAFC。非遞迴的實現思路如下:

對於任一節點P

1)P的左孩子不為空,則將P入棧並將P的左孩子置為當前節點,然後再對當前節點進行相同的處理;

2)P的左孩子為空,則輸出P節點,而後將P的右孩子置為當前節點,看其是否為空;

3)若不為空,則重複1)和2)的操作;

4)若為空,則執行出棧操作,輸出棧頂節點,並將出棧的節點的右孩子置為當前節點,看起是否為空,重複3)和4)的操作;

5)直到當前節點PNULL並且棧為空,則遍歷結束。

   下面以上圖為例詳細分析其中序遍歷的非遞迴實現過程:

首先,從根節點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),遍歷結束。

    根據以上思路,中序遍歷的非遞迴實現程式碼如下:

  1. void in_traverse(BTree pTree)  
  2. {  
  3.     PSTACK stack = create_stack();  //建立一個空棧
  4.     BTree node_pop;                 //用來儲存出棧節點
  5.     BTree pCur = pTree;             //定義指向當前訪問的節點的指標
  6.     //直到當前節點pCur為NULL且棧空時,迴圈結束
  7.     while(pCur || !is_empty(stack))  
  8.     {  
  9.         if(pCur->pLchild)  
  10.         {  
  11.             //如果pCur的左孩子不為空,則將其入棧,並置其左孩子為當前節點
  12.             push_stack(stack,pCur);  
  13.             pCur = pCur->pLchild;  
  14.         }  
  15.         else
  16.         {  
  17.             //如果pCur的左孩子為空,則輸出pCur節點,並將其右孩子設為當前節點,看其是否為空
  18.             printf(”%c ”, pCur->data);  
  19.             pCur = pCur->pRchild;  
  20.             //如果為空,且棧不空,則將棧頂節點出棧,並輸出該節點,
  21.             //同時將它的右孩子設為當前節點,繼續判斷,直到當前節點不為空
  22.             while(!pCur && !is_empty(stack))  
  23.             {  
  24.                 pCur = getTop(stack);  
  25.                 printf(”%c ”,pCur->data);      
  26.                 pop_stack(stack,&node_pop);  
  27.                 pCur = pCur->pRchild;  
  28.             }  
  29.         }  
  30.     }  
  31. }  


   3、後序遍歷的非遞迴實現

根據後序遍歷的順序,先訪問左子樹,再訪問右子樹,後訪問根節點,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的後序遍歷順序為:DEBFCA。後序遍歷的非遞迴的實現相對來說要難一些,要保證根節點在左子樹和右子樹被訪問後才能訪問,思路如下:

對於任一節點P

1)先將節點P入棧;

2)P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已經被輸出,則可以直接輸出節點P,並將其出棧,將出棧節點P標記為上一個輸出的節點,再將此時的棧頂結點設為當前節點;

3)若不滿足2)中的條件,則將P的右孩子和左孩子依次入棧,當前節點重新置為棧頂結點,之後重複操作2);

4)直到棧空,遍歷結束。

   下面以上圖為例詳細分析其後序遍歷的非遞迴實現過程:

首先,設定兩個指標:Cur指標指向當前訪問的節點,它一直指向棧頂節點,每次出棧一個節點後,將其重新置為棧頂結點,Pre節點指向上一個訪問的節點;

Cur首先指向根節點APre先設為NULL,由於A存在左孩子和右孩子,根據操作3),先將右孩子C入棧,再將左孩子B入棧,Cur改為指向棧頂結點B

由於B的也有左孩子和右孩子,根據操作3),將ED依次入棧,Cur改為指向棧頂結點D

由於D沒有左孩子,也沒有右孩子,根據操作2),直接輸出D,並將其出棧,將Pre指向DCur指向棧頂結點E,此時輸出序列為:D

由於E也沒有左右孩子,根據操作2),輸出E,並將其出棧,將Pre指向ECur指向棧頂結點B,此時輸出序列為:DE

由於B的左右孩子已經被輸出,即滿足條件Pre==Cur->lchildPre==Cur->rchild,根據操作2),輸出B,並將其出棧,將Pre指向BCur指向棧頂結點C,此時輸出序列為:DEB

由於C有左孩子,根據操作3),將其入棧,Cur指向棧頂節點F

由於F沒有左右孩子,根據操作2),輸出F,並將其出棧,將Pre指向FCur指向棧頂結點C,此時輸出序列為:DEBF

由於C的左孩子已經被輸出,即滿足Pre==Cur->lchild,根據操作2),輸出C,並將其出棧,將Pre指向CCur指向棧頂結點A,此時輸出序列為:DEBFC

由於A的左右孩子已經被輸出,根據操作2),輸出A,並將其出棧,此時輸出序列為:DEBFCA

此時棧空,遍歷結束。


   根據以上思路,後序遍歷的非遞迴實現程式碼如下:

  1. void beh_traverse(BTree pTree)  
  2. {  
  3.     PSTACK stack = create_stack();  //建立一個空棧
  4.     BTree node_pop;          //用來儲存出棧的節點
  5.     BTree pCur;              //定義指標,指向當前節點
  6.     BTree pPre = NULL;       //定義指標,指向上一各訪問的節點
  7.     //先將樹的根節點入棧
  8.     push_stack(stack,pTree);    
  9.     //直到棧空時,結束迴圈
  10.     while(!is_empty(stack))  
  11.     {  
  12.         pCur = getTop(stack);   //當前節點置為棧頂節點
  13.         if((pCur->pLchild==NULL && pCur->pRchild==NULL) ||   
  14.             (pPre!=NULL && (pCur->pLchild==pPre || pCur->pRchild==pPre)))  
  15.         {  
  16.             //如果當前節點沒有左右孩子,或者有左孩子或有孩子,但已經被訪問輸出,
  17.             //則直接輸出該節點,將其出棧,將其設為上一個訪問的節點
  18.             printf(”%c ”, pCur->data);  
  19.             pop_stack(stack,&node_pop);  
  20.             pPre = pCur;  
  21.         }  
  22.         else
  23.         {  
  24.             //如果不滿足上面兩種情況,則將其右孩子左孩子依次入棧
  25.             if(pCur->pRchild != NULL)  
  26.                 push_stack(stack,pCur->pRchild);  
  27.             if(pCur->pLchild != NULL)  
  28.                 push_stack(stack,pCur->pLchild);  
  29.         }  
  30.     }  
  31. }  

    以上遍歷演算法在VC上實現的輸出結果如下: