二叉樹的前序、中序、后序遍歷(遞歸、非遞歸)實現


本文部分來源於CSDN蘭亭風雨大牛的原創。鏈接為http://blog.csdn.net/ns_code/article/details/12977901

 因為感覺大牛講的很好,所以這里的文字講解采用大牛的,大家可以直接看原創!代碼部分是我自己的,leetcode代碼,可在leetcode Accepted

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

 

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

 

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

 1 /**
 2  * Definition for binary tree
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<int> preorderTraversal(TreeNode *root) {
13       vector<int> root_vec;
14       vector<int> left_vec;
15       vector<int> right_vec;
16       if(root==NULL) return root_vec;
17       root_vec.push_back(root->val);
18       if(root->left!=NULL) left_vec=preorderTraversal(root->left);
19       if(root->right!=NULL) right_vec=preorderTraversal(root->right);
20       root_vec.insert(root_vec.end(),left_vec.begin(),left_vec.end());
21       root_vec.insert(root_vec.end(),right_vec.begin(),right_vec.end());
22       return root_vec;
23     }
24 };
View Code

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

 1 /**
 2  * Definition for binary tree
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10  /*recursive*/
11 class Solution {
12 public:
13     vector<int> inorderTraversal(TreeNode *root) {
14         vector<int> root_vec;
15         vector<int> left_vec;
16         vector<int> right_vec;
17         if(root==nullptr) return root_vec;
18         if(root->left!=nullptr) left_vec=inorderTraversal(root->left);
19         root_vec.push_back(root->val);
20         if(root->right!=nullptr) right_vec=inorderTraversal(root->right);
21         left_vec.insert(left_vec.end(),root_vec.begin(),root_vec.end());
22         left_vec.insert(left_vec.end(),right_vec.begin(),right_vec.end());
23         return left_vec;
24     }
25 };
View Code

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

 1 /**
 2  * Definition for binary tree
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<int> postorderTraversal(TreeNode *root) {
13         vector<int> root_vec;
14         vector<int> left_vec;
15         vector<int> right_vec;
16         if(root==NULL) return root_vec;
17         if(root->left) left_vec=postorderTraversal(root->left);
18         if(root->right) right_vec=postorderTraversal(root->right);
19         root_vec.push_back(root->val);
20         left_vec.insert(left_vec.end(),right_vec.begin(),right_vec.end());
21         left_vec.insert(left_vec.end(),root_vec.begin(),root_vec.end());
22         return left_vec;
23     }
24 };
View Code

 

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

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

 

                                                     

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

 

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

對於任一節點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,因此遍歷結束。

 

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

 1 /**
 2  * Definition for binary tree
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<int> preorderTraversal(TreeNode *root) {
13        vector<int> preorder_vec;
14        TreeNode *p=root;//定義用來指向當前訪問的節點的指針  
15        if(p==NULL) return preorder_vec;//若為空樹,則返回空vector
16        stack<TreeNode *> treenode_stack;//創建一個空棧
17        //直到當前節點p為NULL且棧空時,循環結束 
18        while(p||!treenode_stack.empty())
19        {
20           //從根節點開始,輸出當前節點,並將其入棧,  
21           //同時置其左孩子為當前節點,直至其沒有左孩子,及當前節點為NULL  
22            preorder_vec.push_back(p->val);
23            treenode_stack.push(p);
24            p=p->left;
25            //如果當前節點p為NULL且棧不空,則將棧頂節點出棧,  
26           //同時置其右孩子為當前節點,循環判斷,直至p不為空 
27            while(!p&&!treenode_stack.empty())
28            {
29                p=treenode_stack.top();
30                treenode_stack.pop();
31                p=p->right;
32            }
33        }
34     }
35 };
View Code

 

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

 

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

對於任一節點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),遍歷結束。

 

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

 1 /**
 2  * Definition for binary tree
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10  /*iteratively*/
11 class Solution {
12 public:
13     vector<int> inorderTraversal(TreeNode *root) {
14        stack<TreeNode *> inorder_stack;
15        TreeNode * p=root;
16        vector<int> inorder_vec;
17        if(p==nullptr) return inorder_vec;//若為空樹,則返回空vector
18        while(p||!inorder_stack.empty())
19        {
20            if(p->left!=nullptr)//若左節點不空,當前節點進棧,並使右孩子為當前節點,繼續判斷
21            {
22                inorder_stack.push(p);
23                p=p->left;
24            }
25            else          //如果左孩子為空,則輸出當前節點,並將其右孩子設為當前節點,看其是否為空 
26            {
27                inorder_vec.push_back(p->val);
28                p=p->right;
29                //如果為空,且棧不空,則將棧頂節點出棧,並輸出該節點,  
30                //同時將它的右孩子設為當前節點,繼續判斷,直到當前節點不為空 
31                while(!p&&!inorder_stack.empty())
32                {
33                    p=inorder_stack.top();
34                    inorder_vec.push_back(p->val);
35                    inorder_stack.pop();
36                    p=p->right;
37                }
38            }
39        }
40        return inorder_vec;
41        
42     }
43 };
View Code

3、后序遍歷的非遞歸實現

根據后序遍歷的順序,先訪問左子樹,再訪問右子樹,后訪問根節點,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的后序遍歷順序為: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;

此時棧空,遍歷結束。

   根據以上思路,后序遍歷的非遞歸實現代碼如下:

 

 1 /**
 2  * Definition for binary tree
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<int> postorderTraversal(TreeNode *root) {
13         vector<int> postorder_vec;
14         TreeNode *cur=root; //定義指針,指向當前節點  
15         TreeNode *pre=NULL;//定義指針,指向上一各訪問的節點   
16         if(cur==NULL) return postorder_vec;
17         stack<TreeNode *> postorder_stack;//創建一個空棧  
18         postorder_stack.push(cur);//先將樹的根節點入棧
19         //直到棧空時,結束循環
20         while(!postorder_stack.empty())
21         {
22             cur=postorder_stack.top();//當前節點置為棧頂節點  
23             if((cur->left==NULL&&cur->right==NULL)||
24             ((pre!=NULL)&&(cur->left==pre||cur->right==pre)))
25             {
26             //如果當前節點沒有左右孩子,或者有左孩子或有孩子,但已經被
27             //訪問輸出,則直接輸出該節點,將其出棧,將其設為上一個訪問的節點  
28                 postorder_stack.pop();
29                 postorder_vec.push_back(cur->val);
30                 pre=cur;
31             }
32             else
33             {
34                  //如果不滿足上面兩種情況,則將其右孩子左孩子依次入棧  
35                 if(cur->right!=NULL) postorder_stack.push(cur->right);
36                 if(cur->left!=NULL) postorder_stack.push(cur->left);
37             }
38         }
39     }
40 };
View Code

 

本博文寫的非常粗糙,不斷修改完善中。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM