二叉樹的非遞迴遍歷寫法

tao_bear發表於2020-12-07

二叉樹的非遞迴遍歷寫法

前序遍歷

前序遍歷是按照“中-左-右”的遍歷方式對二叉樹進行訪問

第一種寫法

第一種寫法較為直觀,模擬遞迴棧的做法,先獲取到當前節點,進行列印,然後將該節點的右子樹和左子樹的根節點分別入棧,再將左子樹的根節點出棧,對該節點進行同樣處理。由於我們希望得到“中-左-右”順序,在列印完中節點之後,我們將右節點在左節點之前入棧,根據棧的後入先出的原則,我們先取到對應左節點進行處理。

vector<int> preOrder(TreeNode *root) {
    vector<int> result;
    if (root == nullptr) {
        return result;
    }
    
    stack<TreeNode *> stk;
    TreeNode *node = root;
    
    stk.push(node);
    while (!stk.empty()) {
        node = stk.top();
        stk.pop();
        
        result.push_back(node->val);
        if (node->left) stk.push(node->left);
        if (node->right) stk.push(node->right);
    }
    
    return result;
}

觀察上面程式碼發現,左節點總是壓入棧中取到這個節點的孩子節點之後裡面會被出棧,利用這個性質,我們可以將程式碼優化為只壓入右節點以減少棧操作進行優化。

vector<int> preOrder(TreeNode *root) {
    vector<int> result;
    if (root == nullptr) {
        return result;
    }
    
    stack<TreeNode *>stk;
    TreeNode *node = root;
    while (node != nullptr || !stk.empty()) {
        if (node != nullptr) { // 還能繼續迭代獲取左節點
            result.push_back(node->val);
            if (node->right) {
                stk.push(node->right);
            }
            node = node->left;
        } else { // 左節點已經遍歷到葉節點
            node = stk.top();
            stk.pop();
        }
    }
    
    return result;
}

第二種寫法

另一種是比較通用的模板方法,記住沿路的所有節點,先遍歷所有的左子節點,然後彈出最近的左子節點,取該節點的右子節點,繼續遍歷。

vector<int> preOrder(TreeNode *root) {
    vector<int> result;
    if (root == nullptr) {
        return result;
    }
    
    stack<TreeNode *> stk;
    TreeNode *node = root;
    while (node != nullptr || !stk.empty()) {
        while (node != nullptr) {
            result.push_back(node->val);
            stk.push(node);
            node = node->left;
        }
        
        node = stk.top()->right;
        stk.pop();
    }
    
    return result;
}

中序遍歷

中序遍歷遵循“左-中-右”的遍歷原則對二叉樹進行訪問。

中序遍歷先訪問的是二叉樹的頂部節點,然後一層層向下訪問,直到達到樹左邊的最底部,再開始處理節點,由於處理順序和訪問順序不一致,需要紀錄下整個訪問路徑,對應前序遍歷的第二種解法,在出棧的時候列印二叉樹的節點。

vector<int> inOrder(TreeNode *root) {
    vector<int> result;
    if (root == nullptr) {
        return result;
    }
    
    stack<TreeNode *> stk;
    TreeNode *node = root;
    while (node != nullptr || !stk.empty()) {
        while (node != nullptr) {
            stk.push(node);
            node = node->left;
        }
        
        node = stk.top();
        stk.pop();
        
        result.push_back(node->val);
        node = node->right;
    }
    return result;
}

或者,將中間的while語句提升到外層

vector<int> inOrder(TreeNode *root) {
    vector<int> result;
    if (root == nullptr) {
        return result;
    }
    
    stack<TreeNode *> stk;
    TreeNode *node = root;
    while (node != nullptr || !stk.empty()) {
        if (node != nullptr) {
            stk.push(node);
            node = node->left;
        } else {
            node = stk.top();
            stk.pop();
            
            result.push_back(node->val);
            node = node->right;
        }
    }
    return result;
}

後序遍歷

後序遍歷的順序為“左-右-中”。

第一種寫法(雙棧寫法或者需要額外逆序操作)

按照後序遍歷的特點,我們可以先獲得“中-右-左”的序列,再進行逆序或者棧操作顛倒輸出順序得到結果(思路見前序遍歷的第一種寫法)。

vector<int> postOrder(TreeNode *root) {
    vector<int> result;
    if (root == nullptr) {
        return result;
    }
    
    TreeNode *node = root;
    stack<TreeNode *> stk;
    
    stk.push(node);
    while (!stk.empty()) {
        node = stk.top();
        stk.pop();
        result.push_back(node->val);
        if (node->left) stk.push(node->left);
        if (node->right) stk.push(node->right);
    }
    reverse(result.begin(), result.end());
    return result;
}

第二種寫法(單棧寫法)

後序遍歷在整個過程中需要將左子樹和右子樹都訪問完成才會繼續訪問根節點。在前序遍歷的第二種寫法和中序遍歷的迭代寫法中,我們記住了整個訪問路徑,在出棧時,我們還要拿到該節點的右節點資訊,訪問過右節點之後還會再拿到該節點的資訊,所以一個節點訪問兩次,我們需要記住在右節點已經訪問之後才訪問根節點。所以,我們設定 last,在訪問完節點之後賦值為當前節點。

vector<int> postOrder(TreeNode *root) {
    vector<int> result;
    if (root == nullptr) {
        return result;
    }
    
    TreeNode *node = root;
    TreeNode *last = nullptr;
    stack<TreeNode *> stk;
    while (node != nullptr || !stk.empty()) {
        while (node != nullptr) {
            stk.push(node);
            node = node->left;
        }
        
        node = stk.top();
        if (node->right == nullptr || node->right == last) {
            result.push_back(node->val);
            last = node; // 將last置為當前訪問節點
            node = nullptr; // 對當前元素訪問完成之後,需要繼續彈出棧頂元素,所以將node置為nullptr
            stk.pop();
        } else {
            node = node->right;
        }
    }
    
    return result;
}

 

相關文章