遍歷二叉樹的迭代和遞迴方法

不妨不妨,來日方長發表於2020-12-12

二叉樹的問題,一定要明白到底應該深度優先(前中後序)還是廣度優先(層序遍歷)

最基本的遍歷方式:深度優先和廣度優先

  深度優先:前、中、後序(遞迴法和迭代法均可)

  廣度優先:層次遍歷(迭代法)

其實就是遞迴的一種實現結構,也就是說前中後序遍歷的邏輯其實都是可以藉助棧使用非遞迴的方式來實現的;

廣度優先遍歷(層序遍歷)的實現一般使用佇列來實現,這也是佇列先進先出的特點所決定的,因為需要先進先出的結構,才能一層一層的來遍歷二叉樹

二叉樹節點的定義框架:

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

二叉樹的遞迴遍歷框架:

/*二叉樹的遍歷框架*/
void traverse(TreeNode root)
{
    //前序遍歷:先訪問根節點,再前序訪問左子樹,再訪問右子樹
    traverse(root->left);
    //中序遍歷:先中序訪問左子樹,再訪問根節點,再訪問右子樹
    traverse(root->right);
    //後續遍歷:先後續訪問左子樹,再訪問右子樹,再訪問根節點
}

一、二叉樹的前序遍歷:迭代和遞迴

class Solution {
public:
    //vector<int> result;//遞迴的話定義在這裡
    vector<int> preorderTraversal(TreeNode* root) {
        //遞迴方式
        /*
        if(root == nullptr)
            return {};
        result.push_back(root->val);
        preorderTraversal(root->left);
        preorderTraversal(root->right);
        return result;
        */
        //當然可以使用迭代解法,因為遞迴本身就是用棧來實現的,可以通過棧來迭代操作
        //但是要注意棧的特性是後入先出,前序的話,就是先放入根節點賦值操作彈出,再放入右節點、左節點,再彈出,這樣左節點就會先出,先賦值操作,就是前序了
        stack<TreeNode*> sta;
        vector<int> result;
        sta.push(root);
        while(!sta.empty()) {
            int size = sta.size();
            for(int i=0; i<size; i++) {
                TreeNode* node = sta.top();
                sta.pop();
                result.push_back(node->val);
                if(node->right)
                    sta.push(node->right);
                if(node->left)
                    sta.push(node->left);
            }
        }
        return result;
    }
};

二、二叉樹的後序遍歷:迭代和遞迴

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //vector<int> result;//遞迴解法定義在這裡
    vector<int> postorderTraversal(TreeNode* root) {
        /*
        if(root == nullptr)
            return {};
        postorderTraversal(root->left);
        postorderTraversal(root->right);
        result.push_back(root->val);
        return result;
        */
        //本題還可以採用迭代解法,因為遞迴就是用棧來實現的
        //考慮實現的過程
        //後序遍歷是左右中的順序,但是我們在迭代的時候肯定會先訪問根節點,也就是中間的節點,所以考慮先訪問和處理中間節點,再處理右節點,再處理左邊節點,最後將結果翻轉就行了
        stack<TreeNode*> sta;
        vector<int> result;
        sta.push(root);
        while(!sta.empty()) {
            int size = sta.size();
            for(int i=0; i<size; i++) {
                TreeNode* node = sta.top();
                sta.pop();
                result.push_back(node->val);
                if(node->left)
                    sta.push(node->left);
                if(node->right)
                    sta.push(node->right);
            }
        }
        reverse(result.begin(), result.end());
        return result;
    }
};

三、二叉樹的中序遍歷:迭代和遞迴

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //vector<int>result;//遞迴寫法這裡定義
    vector<int> inorderTraversal(TreeNode* root) {
        /*遞迴解法
        if(root == nullptr)
            return {};
        inorderTraversal(root->left);
        result.push_back(root->val);
        inorderTraversal(root->right);
        return result;
        */
        //還能採用迭代解法,用棧來解決,因為遞迴本身就是用棧來實現的,因此是完全行得通的
        //中序的順序是左中右,那出棧的時候,處理的順序肯定是右中左
        //搞清楚訪問和處理的概念
        //訪問:將節點入棧
        //處理:將節點的值放入結果集
        //中序的訪問和處理的順序是不一樣的,所以要藉助指標進行訪問,也就是將節點放入棧中,用棧來做處理,也就是放入結果集
        vector<int> result;
        stack<TreeNode*> sta;
        TreeNode* cur = root;
        while(cur != nullptr || !sta.empty()) {
            if(cur != nullptr) {//指標用來訪問節點,訪問到左邊最底層的時候,指標和要開始處理的位置就一樣了
                sta.push(cur);//將訪問的節點放進棧
                cur = cur->left;//最左的子節點最後放進去,所以會先出棧    左
            }
            else {
                cur = sta.top();
                sta.pop();
                result.push_back(cur->val);                             //
                cur = cur->right;                                       //
            }
        }
    }
};

四、二叉樹的層序遍歷:迭代和遞迴

/**
 * 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<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;//建立一個佇列,層序遍歷樹的需要用佇列來實現,佇列中是二叉樹的節點
        if(root != nullptr)
            que.push(root);//如果頭結點不為空的話,先將頭結點放到佇列中,因為頭結點也就是第一行,只有這一個元素,所以直接放進去
        vector<vector<int>> result;//定義返回值,返回的是一個二維陣列
        while(!que.empty()) {
            int size = que.size();//同一行可能不止一個元素,要迴圈都進行遍歷,又因為下面要進行pop操作,que.size()是一個變化的值,所以這裡儲存數量
            vector<int> vec;//用於臨時儲存每一行的節點值,最後統一存入返回的二維陣列中
            for(int i=0; i<size; i++) {
                TreeNode* node = que.front();
                que.pop();//
                vec.push_back(node->val);
                if(node->left)
                    que.push(node->left);//將這個節點的左右子節點放入佇列中
                if(node->right)
                    que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};

掌握了層序遍歷的模板,別的層序遍歷相關的題只要稍微改動幾行程式碼就可以解決了。

遇到二叉樹的題目,一定要想一想到底是用深度優先遍歷還是廣度優先遍歷,到底使用迭代法還是用遞迴法。

相關文章