有兩種通用的遍歷樹的策略:
深度優先搜索(DFS)
在這個策略中,我們採用深度作爲優先級,以便從跟開始一直到達某個確定的葉子,然後再返回根到達另一個分支。
深度優先搜索策略又可以根據根節點、左孩子和右孩子的相對順序被細分爲前序遍歷,中序遍歷和後序遍歷。
寬度優先搜索(BFS)
我們按照高度順序一層一層的訪問整棵樹,高層次的節點將會比低層次的節點先被訪問到。
作者:LeetCode
鏈接:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/er-cha-shu-de-qian-xu-bian-li-by-leetcode/
2 public: 3 vector<int> res; 4 vector<int> preorderTraversal(TreeNode* root){ 5 if(root){ 6 res.push_back(root -> val); 7 preorderTraversal(root -> left); 8 preorderTraversal(root -> right); 9 }return res; 10 } 11 };
這個就算公共變量,可以直接用。
我的輸入
[1,null,2,3]
我的答案
[1,2,3]
1 class Solution { 2 public: 3 vector<int> preorderTraversal(TreeNode* root){ 4 vector<int> res; 5 if(root){ 6 res.push_back(root -> val); 7 preorderTraversal(root -> left); 8 preorderTraversal(root -> right); 9 }return res; 10 } 11 };
這個res就是局部變量,輸入0,null,1,2 輸出就是1,而沒有其他值。
這屬於程序接口問題,preorderTraversal(TreeNode* root){}相當於一個函數,裏面的內容算是臨時變量,所以只有一個值出來。
再來改進
首先 遍歷可以不用遞歸 ( 比如莫里斯遍歷 )這樣能提高效率
如果用遞歸可以搞兩個函數 外部函數聲明臨時變量 然後內部函數每次往這個臨時變量裏面push 這樣就行了
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: vector<int> ret; vector<int> preorderTraversal(TreeNode* root) { pre_travel(root); return ret; } void pre_travel(TreeNode* root) { if(root)//當前結點非空 { ret.push_back(root->val);//訪問根節點 pre_travel(root->left);//遞歸左子樹 pre_travel(root->right);//遞歸右子樹 } } };
再就是,遞歸的本質其實可以理解成棧。 遞歸的本質就是壓棧,瞭解遞歸本質後就完全可以按照遞歸的思路來迭代。
怎麼壓,壓什麼?壓的當然是待執行的內容,後面的語句先進棧,所以進棧順序就決定了前中後序。
我們需要一個標誌區分每個遞歸調用棧,這裏使用nullptr來表示。
具體直接看註釋,可以參考文章最後“和遞歸寫法的對比”。先序遍歷看懂了,中序和後序也就秒懂。
鏈接:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/miao-sha-quan-chang-ba-hou-lang-by-sonp/
class Solution { public: vector<int> preorderTraversal(TreeNode* root) { vector<int> res; //保存結果 stack<TreeNode*> call; //調用棧 if(root!=nullptr) call.push(root); //首先介入root節點 while(!call.empty()){ TreeNode *t = call.top(); call.pop(); //訪問過的節點彈出 if(t!=nullptr){ if(t->right) call.push(t->right); //右節點先壓棧,最後處理 if(t->left) call.push(t->left); call.push(t); //當前節點重新壓棧(留着以後處理),因爲先序遍歷所以最後壓棧 call.push(nullptr); //在當前節點之前加入一個空節點表示已經訪問過了 }else{ //空節點表示之前已經訪問過了,現在需要處理除了遞歸之外的內容 res.push_back(call.top()->val); //call.top()是nullptr之前壓棧的一個節點,也就是上面call.push(t)中的那個t call.pop(); //處理完了,第二次彈出節點(徹底從棧中移除) } } return res; } }; //先序遍歷
class Solution { public: vector<int> postorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> call; if(root!=nullptr) call.push(root); while(!call.empty()){ TreeNode *t = call.top(); call.pop(); if(t!=nullptr){ call.push(t); //在右節點之前重新插入該節點,以便在最後處理(訪問值) call.push(nullptr); //nullptr跟隨t插入,標識已經訪問過,還沒有被處理 if(t->right) call.push(t->right); if(t->left) call.push(t->left); }else{ res.push_back(call.top()->val); call.pop(); } } return res; } }; 後序遍歷
void dfs(t){ //進入函數表示“訪問過”,將t從棧中彈出 dfs(t->left); //因爲要訪問t->left, 所以我先把函數中下面的信息都存到棧裏。 //依次call.push(t->right), call.push(t)【t第二次入棧】, call.push(nullptr)【標識t二次入棧】, call.push(t->left)。 //此時t並沒有被處理(賣萌)。棧頂是t->left, 所以現在進入t->left的遞歸中。 //res.push_back(t->val) t.賣萌(); //t->left 處理完了,t->left被徹底彈出棧。 //此時棧頂是nullptr, 表示t是已經訪問過的。那麼我現在需要真正的處理t了(即,執行賣萌操作)。 //賣萌結束後,t 就被徹底彈出棧了。 dfs(t->right); } //中序遍歷