二叉樹遍歷的三種方法的非遞歸版本

二叉樹遍歷的三種方法的非遞歸版本

二叉樹遍歷雖然是一個老生常談的問題,但在面試中經(jīng)常遇見,最近在刷leetcode的時候碰到了用前序,中序和后序遍歷二叉樹,遂來總結(jié)一下思路。

前序遍歷

題目參考leetcode 144 Binary Tree Preorder Traversal

從根節(jié)點出發(fā),按照根->左子樹->右子樹的順序遞歸訪問整個二叉樹。

例如:輸入 [1,2,3,null,4,5,null],輸出 [1,2,4,3,5](可自行畫圖體會)

遞歸版本

代碼十分好寫,思路就是在遞歸訪問左右子樹之前先訪問根節(jié)點

// 定義樹節(jié)點
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x):val(x),left(NULL),right(NULL){}
};

// 遞歸函數(shù),將前序遍歷的結(jié)果放入result中
void preorderCore(TreeNode* pNode,vector<int>& result){
    if(pNode == NULL){
        return;
    }
    result.push_back(pNode->val);
    preorderCore(pNode->left, result);
    preorderCore(pNode->right, result);
}

// 主函數(shù)
vector<int> preorderTraversal(TreeNode* root) {
    if(root == NULL){
        return {};
    }
    vector<int> result;
    preorderCore(root, result);
    return result;
}

非遞歸版本

實際上前序遍歷的非遞歸代碼是三種遍歷方式中最好寫的一個。

思路:使用一個棧保存待訪問的節(jié)點,使用pCur保存當(dāng)前正在訪問的節(jié)點,大致思路和DFS遍歷很類似,就是一路沿左子樹向下,直到左子節(jié)點為空再通過棧進行回退操作,直到棧為空,詳細(xì)代碼如下:

vector<int> preorderTraversal(TreeNode* root) {
    if(root == NULL){
        return {};
    }
    vector<int> result;
    stack<TreeNode*> nodes;
    TreeNode* pCur = root;
    while(!nodes.empty() || pCur != NULL){
        if(pCur != NULL){
            nodes.push(pCur);
            result.push_back(pCur->val);
            pCur = pCur->left;
        }
        else{
            TreeNode* tmp = nodes.top();
            nodes.pop();
            pCur = tmp->right;
        }
    }
    return result;
}

上面的代碼是我認(rèn)為最簡潔的代碼,而且這個利用DFS遍歷二叉樹的模板套路只要略加修改就可以應(yīng)用到剩下的兩個遍歷中。

中序遍歷

題目參考leetcode 94 Binary Tree Inorder Traversal

從根節(jié)點出發(fā),按左子樹->父節(jié)點->右子樹的順序遞歸訪問。

例如:輸入 [1,2,3,null,4,5,null],輸出 [2,4,1,5,3](可自行畫圖體會)

遞歸版本

同樣非常好寫,把握訪問順序即可,代碼如下:

// 樹節(jié)點定義如前序遍歷中定義的一樣
void inorderCore(TreeNode* pNode,vector<int>& result){
    if(pNode == NULL){
        return;
    }
    inorderCore(pNode->left, result);
    result.push_back(pNode->val);
    inorderCore(pNode->right, result);
}

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> result;
    if(root == NULL){
        return result;
    }
    inorderCore(root, result);
    return result;
}

非遞歸版本

如上面所說,可以把前序遍歷的非遞歸代碼略加修改應(yīng)用到這里即可,實際上就是修改了將節(jié)點加入結(jié)果數(shù)組的順序,代碼如下:

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> result;
    if(root == NULL){
        return result;
    }
    stack<TreeNode*> nodes;
    TreeNode* pCur = root;
    while(!nodes.empty() || pCur != NULL){
        if(pCur != NULL){
            nodes.push(pCur);
            pCur = pCur->left;
        }
        else{
            TreeNode* tmp = nodes.top();
            result.push_back(tmp->val);
            nodes.pop();
            pCur = tmp->right;
        }
    }
    return result;
}

后序遍歷

題目參考leetcode 145 Binary Tree Postorder Traversal

從根節(jié)點出發(fā),按左子樹->右子樹->父節(jié)點的順序遞歸訪問。

例如:輸入 [1,2,3,null,4,5,null],輸出 [4,2,5,3,1](可自行畫圖體會)

遞歸版本

同樣按照訪問順序直接寫即可:

// 樹節(jié)點定義同上
void postorderCore(TreeNode* pNode,vector<int>& result){
    if(pNode == NULL){
        return;
    }
    postorderCore(pNode->left, result);
    postorderCore(pNode->right, result);
    result.push_back(pNode->val);
}

vector<int> postorderTraversal(TreeNode* root) {
    if(root == NULL){
        return {};
    }
    vector<int> result;
    postorderCore(root, result);
    return result;
}

非遞歸版本

可以說后序遍歷的非遞歸代碼是三個里面最難的一個,不過按照上面的套路來其實也不難。

注意坑點:由于父節(jié)點需要最后一個彈出,所以如果照搬前面的思路是會出現(xiàn)死循環(huán)的,即重復(fù)訪問父節(jié)點和它的子樹,當(dāng)棧頂元素是某個父節(jié)點時我們并不知道它的孩子沒有沒有被訪問過,會重復(fù)的將它的孩子們壓入棧彈出棧,重復(fù)訪問,解決辦法為使用一個指針lastVisited記錄最后訪問的節(jié)點,當(dāng)棧頂元素為父節(jié)點時,判斷l(xiāng)astVisited是否是它的右節(jié)點,如果是,則代表它的孩子們已經(jīng)遍歷過,直接彈出該父節(jié)點即可,具體代碼如下:

vector<int> postorderTraversal(TreeNode* root) {
    if(root == NULL){
        return {};
    }
    vector<int> result;
    stack<TreeNode*> nodes;
    TreeNode* pCur = root;
    TreeNode* lastVisited = NULL;
    while(!nodes.empty() || pCur != NULL){
        if(pCur != NULL){
            nodes.push(pCur);
            pCur = pCur->left;
        }
        else{
            TreeNode* tmp = nodes.top();
            if(tmp->right != NULL && lastVisited != tmp->right){
                pCur = tmp->right;
            }
            else{
                result.push_back(tmp->val);
                nodes.pop();
                lastVisited = tmp;
            }
        }
    }
    return result;
}

不得不提一下,很多人用前序遍歷的結(jié)果reverse一下作為后序遍歷的答案,雖然結(jié)果是正確的,但是這其實是一種欺騙做法,并沒有按照后序遍歷應(yīng)用的順序訪問節(jié)點啊,攤手。

總結(jié)

實際上這三種遍歷方式的非遞歸算法都遵循了某一規(guī)律,只要熟悉了遍歷順序,寫出代碼也不是很難~希望看完這篇文章能對大家理解二叉樹的遍歷有一點幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容