首先定義二叉樹的存儲結構:
1 struct TreeNode { 2 int val; 3 TreeNode *left; 4 TreeNode *right; 5 6 TreeNode(int v, TreeNode* l = NULL, TreeNode *r = NULL) 7 :val(v), left(l), right(r) {} 8 };
1.遞歸的方法(《編程之美》3.10)
二叉樹本身就帶有遞歸屬性,通常我們可以用遞歸方法解決。假設要訪問第k層節點,那么其實可以轉皇城分別訪問“以該二叉樹根節點的左右子節點為根節點的兩棵子樹”中層次為k-1的節點。此方法需要求出二叉樹的深度,其實可以直接訪問到二叉樹某一層次失敗的時候返回就可以了。
這個方法的問題是遞歸調用,效率較低。而且對每一層的訪問都需要從根節點開始,效率極差。
最壞的情況下(不平衡樹)時間復雜度為O(n^2),空間復雜度O(1)
1 //輸出以root為根節點中的第level層中的所有節點(從左至右),成功返回1 2 //失敗返回0 3 //root為二叉樹根節點 4 //level為層次數,其中根節點為第0層 5 int PrintNodeAtLevel(TreeNode *root, int level) { 6 if (!root || level < 0) return 0; 7 if (level == 0){ 8 cout<<root->val; 9 return 1; 10 } 11 12 return PrintNodeAtLevel(root->left, level - 1) + PrintNodeAtLevel(root->right, level - 1); 13 } 14 15 //層次遍歷二叉樹 16 //root,二叉樹的根節點 17 void LevelOrder(TreeNode *root) { 18 for (int level = 0; ; level++) { 19 if (!PrintNodeAtLevel(root, level)) 20 break; 21 cout<<endl; 22 } 23 }
2. 使用數組和兩個游標的方法(《編程之美》 3.10)
在訪問k層的時候,我們只需要知道k-1層的信息就足夠了,所以在訪問第k層的時候,要是能夠知道k-1層的節點信息,就不再需要從根節點開始遍歷了。
根據上述分析,可以從根節點出發,依次將每一層的根節點從左往右壓入一個數組,並並用一個游標cur記錄當前訪問的節點,另一個游標last指示當前層次的最后一個節點的下一個位置,以cur===last作為當前層次訪問結束的條件,在訪問某一層的同時將該層的所有節點的子節點壓入數組,在訪問完某一層之后,檢查是否還有新的層次可以訪問,直到檢查完所有的層次(不再有新的節點可以訪問)
這種方法需要一個vector一直存儲所有節點,空間效率較差。
時間復雜度為O(n),空間復雜度為O(n)
1 void LevelOrder(TreeNode *root) { 2 if (root == NULL) return; 3 vector<TreeNode *> vec; //這里使用stl中的vector代替數組,可利用到 4 //其動態擴展的屬性 5 vec.push_back(root); 6 int cur = 0, last = vec.size(); 7 while (cur < vec.size()) { 8 last = vec.size(); 9 10 while (cur < last) { 11 cout<<vec[cur]->val; 12 if (vec[cur]->left) 13 vec.push_back(vec[cur]->left); 14 if(vec[cur]->right) 15 vec.push_back(vec[cur]->right); 16 ++cur; 17 } 18 cout<<endl; 19 } 20 }
3. 兩個隊列的方法
廣度優先搜索的思想。使用兩個隊列,一個記錄當前層的節點,另一個記錄下一層的節點。輸出當前層節點后交換,使下一層的節點稱為當前層的節點。
時間復雜度O(n),空間復雜度O(1)
1 void LevelOrder(TreeNode *root) { 2 if (root == NULL) return ; 3 4 queue<TreeNode *> current, next; 5 6 current.push(root); 7 while (!current.empty()) { 8 while (!current.empty()) { 9 TreeNode * p = current.front(); 10 cout<<p->val<<" "; 11 current.pop(); 12 if (p->left) 13 next.push(p->left); 14 if (p->right) 15 next.push(p->right); 16 } 17 cout<<endl; 18 swap(next, current); 19 } 20 }
4.使用一個隊列和兩個標記的方法
使用current記錄當前節點的數量,nextlevel記錄下一層節點的數量。當current==0時就將下一層置為當前層。
1 void LevelOrder(TreeNode *root) { 2 if (root == NULL) return; 3 4 queue<TreeNode *> q; 5 q.push(root); 6 int nextlevel = 0; //記錄下一層節點的數量 7 int current = 1; //記錄當前層節點的數量 8 9 while (!q.empty()) { 10 TreeNode *p = q.front(); 11 q.pop(); 12 --current; 13 cout<<p->val<<" "; 14 15 if (p->left) { 16 q.push(p->left); 17 ++nextlevel; 18 } 19 if (p->right) { 20 q.push(p->right); 21 ++nextlevel; 22 } 23 24 if(current == 0) { 25 cout<<endl; 26 swap(current, nextlevel); 27 } 28 } 29 }
5. 使用一個隊列加一個標記的方法
用隊列暫存儲節點,每當一層節點進入隊列,就在最后加入一個空指針表示當前層結束。
1 void LevelOrder(TreeNode *root) { 2 if (root == NULL) return; 3 4 queue<TreeNode *> q; 5 q.push(root); 6 q.push(0); 7 while (!q.empty()) { 8 TreeNode *p =q.front(); 9 q.pop(); 10 if (p) { 11 cout<<p->val<<" "; 12 if (p->left) 13 q.push(p->left); 14 if (p->right) 15 q.push(p->right); 16 //當發現空指針(結束信號時),要檢查隊列是夠還有節點 17 //如果沒有的話還插入新的結束信號,則會造成死循環 18 } else if (!q.empty()) { 19 q.push(0); 20 cout<<endl; 21 } 22 } 23 }
參考資料:
1.《編程之美》
2. 《劍指offer》
2. http://www.cnblogs.com/miloyip/archive/2010/05/12/binary_tree_traversal.html