二叉樹的四種遍歷方式:遞歸、非遞歸+棧、Morris(後序非遞歸還有一種單棧和雙棧的不同版本)

本文參考:
參考文章1
參考文章2
代碼中加入了一些自己的理解

/* 二叉樹的四種遍歷方式
*/
#include <iostream>
#include <stack>
using namespace std;

// 二叉樹節點的定義
class TreeNode{

public:

    char val;
    //int val;
    TreeNode *left, *right;
    TreeNode(int val){
        this->val = val;
        this->left = this->right = NULL;
    }
};

// 遞歸方式,每個結點只遍歷一次,時間複雜度O(1),遞歸最多調用n次,空間複雜度O(n)

// 先序:根-》遞歸左-》遞歸右
void preOrder(TreeNode* root){
    if(root==NULL)
        return;
    cout << root->val << endl;
    preOrder(root->left);
    preOrder(root->right);
}

// 中序:遞歸左-》根-》遞歸右
void inOrder(TreeNode* root){
    if(root==NULL)
        return;
    inOrder(root->left);
    cout << root->val << endl;
    inOrder(root->right);
}
// 後序:遞歸左-》遞歸右-》根
void postOrder(TreeNode* root){
    if(root==NULL)
        return;
    postOrder(root->left);
    postOrder(root->right);
    cout << root->val << endl;
}

// 非遞歸,使用棧,每個節點只需遍歷一次,時間複雜度O(n),使用棧,只需壓入或彈出各一次,空間複雜度O(n)

// 先序:根壓入,從棧頂彈出結點,並訪問,
// 壓入當前右孩子,壓入當前左孩子,則出棧順序與先序遍歷一致
void preOrder2(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    stk.push(root);
    while(!stk.empty()){
        TreeNode* pNode = stk.top();
        stk.pop();
        cout << pNode->val << endl;
        if(pNode->right!=NULL)
            stk.push(pNode->right);
        if(pNode->left!=NULL)
            stk.push(pNode->left);
    }
}

// 中序:初始指針pNode指向根,
// 若pNode不空,則pNode壓入,pNode指向當前左孩子,一直到最左,
// 若pNode爲空,從棧頂彈出結點,並訪問,pNode當前右孩子
void inOrder2(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    TreeNode* pNode = root;
    while(pNode!=NULL || !stk.empty()){
        if(pNode!=NULL){
            stk.push(pNode);
            pNode = pNode->left;
        }
        else{
            pNode = stk.top();
            cout << pNode->val << endl;
            stk.pop();
            pNode = pNode->right;
        }
    }
}

// 後序,使用2個棧:設置兩個棧stk, stk2;
// 將根結點壓入第一個棧stk;彈出stk棧頂的結點,並把該結點壓入第二個棧stk2;
// 將當前結點的左孩子和右孩子先後分別入棧stk;當所有元素都壓入stk2後,依次彈出stk2的棧頂結點,並訪問之。
// 第一個棧的入棧順序是:根結點,左孩子和右孩子;壓入第二個棧的順序是:根結點,右孩子和左孩子。
// 因此,第二個棧彈出的順序就是:左孩子,右孩子和根結點。
void postOrder2(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    stack<TreeNode*> stk2;
    stk.push(root);
    while(!stk.empty()){
        TreeNode* pNode = stk.top();
        stk.pop();
        stk2.push(pNode);
        if(pNode->left!=NULL)
            stk.push(pNode->left);
        if(pNode->right!=NULL)
            stk.push(pNode->right);
    }
    while(!stk2.empty()){
        cout << stk2.top()->val << endl;
        stk2.pop();
    }

}

// 後序,使用1個棧:
// 每次循環開始時,當前結點更新爲棧頂
// 首先向下遍歷,指向之前訪問的結點的指針prev爲空時,或者當前訪問的是prev的孩子時,繼續向下訪問左孩子並壓入,左孩子沒有則訪問右孩子並壓入
// 之後是從左孩子向上遍歷,即當前訪問的是prev的父結點時,如果父節點有右孩子,繼續向下訪問右孩子,並壓入
// prev不爲空,並且當前訪問的節點也不是prev的孩子,說明已經到達葉子節點或父結點,之後輸出當前節點,並彈出
// 每個步驟操作完成後,將prev更新爲當前結點
void postOrder3(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    stk.push(root);
    TreeNode* prev = NULL;//指向上次訪問的節點,即爲父節點或者葉節點
    while(!stk.empty()){
        TreeNode *pNode = stk.top();
        if(!prev || prev->left == pNode || prev->right == pNode){//之前沒有剛問過的點,或者當前是prev的孩子,則繼續向下遍歷並壓入
            if(pNode->left)
                stk.push(pNode->left);
            else if(pNode->right)
                stk.push(pNode->right);
        }
        else if(pNode->left==prev){//若左孩已經訪問過
            if(pNode->right)//則繼續訪問右孩,並壓入
                stk.push(pNode->right);
        }
        else{//到達葉節點,或者父節點
            cout << pNode->val << endl;
            stk.pop();
        }
        prev = pNode;
    }
}

// Morris 遍歷:線索二叉樹,在O(1)空間內,O(n)時間內完成遍歷,通過修改葉子結點的左右空指針指向前驅或後記

// 中序:
// 1. 如果當前節點的左孩子爲空,則輸出當前節點並將其右孩子作爲當前節點。
// 2. 如果當前節點的左孩子不爲空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。
//  a) 如果前驅節點的右孩子爲空,將它的右孩子設置爲當前節點。當前節點更新爲當前節點的左孩子。
//  b) 如果前驅節點的右孩子爲當前節點,將它的右孩子重新設爲空(恢復樹的形狀)。輸出當前節點。當前節點更新爲當前節點的右孩子。
// 3. 重複以上1、2直到當前節點爲空。
// 使用兩個輔助指針,空間複雜度O(1),
// 時間複雜度O(n):n個結點二叉樹有n-1條邊,每條邊最多有兩次,一次用於找前驅,一次用於訪問該結點
void inOrderMorris(TreeNode* root){
    TreeNode* cur = root;
    TreeNode* prev = NULL;
    while(cur!=NULL){
        if(cur->left == NULL){      // 1. (表示左子樹已經遍歷完,訪問其前驅)
            cout << cur->val << endl;
            cur = cur->right;
        }
        else{
            //找到cur的前驅prev,即爲cur的左孩子的最右孩子
            prev = cur->left;
            while(prev->right!=NULL && prev->right!=cur){
                prev = prev->right;
            }
            //已經找到prev
            if(prev->right==NULL){  // 2.a)
                prev->right = cur;  // 添加線索
                cur = cur->left;    // 向下左遍歷繼續爲每個中結點找前驅
            }
            else{                           // 2.b)
                prev->right = NULL;         // 第二次遍歷到中結點,刪除線索
                cout << cur->val << endl;   // 打印中結點
                cur = cur->right;           // 訪問右子樹,右子樹不再有右子樹時,cur爲空,程序終止
            }
        }
    }
}

// 前序:與中序相似,在於輸出的順序
// 1. 如果當前節點的左孩子爲空,則輸出當前節點並將其右孩子作爲當前節點。
// 2. 如果當前節點的左孩子不爲空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。
//  a) 如果前驅節點的右孩子爲空,將它的右孩子設置爲當前節點。輸出當前節點(在這裏輸出,這是與中序遍歷唯一一點不同)。當前節點更新爲當前節點的左孩子。
//  b) 如果前驅節點的右孩子爲當前節點,將它的右孩子重新設爲空。當前節點更新爲當前節點的右孩子。
// 3. 重複以上1、2直到當前節點爲空。
// 時間、空間複雜度與中序相同
void preOrderMorris(TreeNode* root){
    TreeNode* cur = root;
    TreeNode* prev = NULL;
    while(cur!=NULL){
        if(cur->left == NULL){
            cout << cur->val << endl;
            cur = cur->right;
        }
        else{
            prev = cur->left;
            while(prev->right!=NULL && prev->right!=cur){
                prev = prev->right;
            }

            if(prev->right == NULL){ // 與中序唯一不同之處
                cout << cur->val << endl;
                prev->right = cur;
                cur = cur->left;
            }
            else{
                prev->right = NULL;
                cur = cur->right;
            }
        }
    }
}

// 後序:稍複雜,需要建立臨時結點dump,令其左孩子是root,
// 需要子過程:倒序輸出某兩個節點之間路徑上的各個節點
// 當前節點設置爲臨時節點dump。
// 1. 如果當前節點的左孩子爲空,則將其右孩子作爲當前節點。
// 2. 如果當前節點的左孩子不爲空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。
//  a) 如果前驅節點的右孩子爲空,將它的右孩子設置爲當前節點。當前節點更新爲當前節點的左孩子。
//  b) 如果前驅節點的右孩子爲當前節點,將它的右孩子重新設爲空。倒序輸出從當前節點的左孩子到該前驅節點這條路徑上的所有節點。當前節點更新爲當前節點的右孩子。
// 3. 重複以上1、2直到當前節點爲空。

void reverseFromTo(TreeNode* from, TreeNode* to){
    if(from==to)
        return;
    TreeNode *x = from;//x爲路徑頭的結點
    TreeNode *y = from->right;//y爲待插入到路徑頭的節點
    TreeNode *z;//下一帶插入點
    // y不斷向x頭位置前插
    while(true){
        z = y->right;
        y->right = x;
        x = y;
        y = z;
        if(x == to)
            break;
    }
}

void printReverse(TreeNode* from, TreeNode* to){
    reverseFromTo(from,to);//顛倒from到to結點的路徑
    TreeNode* pNode = to;
    while(true){
        cout << pNode->val << endl;
        if(pNode==from)
            break;
        pNode = pNode->right;
    }
    reverseFromTo(to,from);//恢復樹的形狀

}
void postOrderMorris(TreeNode* root){
    if(root == NULL)
        return;
    TreeNode* dump = new TreeNode('z');
    dump->left = root;
    TreeNode* pNode = dump;
    TreeNode* pPrev = NULL;
    while(pNode!=NULL){//當前節點不爲空時
        if(pNode->left==NULL)
            pNode = pNode->right;
        else{
            //找前驅pPrev
            pPrev = pNode->left;
            while(pPrev->right!=NULL && pPrev->right!=pNode){
                pPrev = pPrev->right;
            }
            //前驅pPrev已找到
            if(pPrev->right==NULL){
                pPrev->right = pNode;
                pNode = pNode->left;
            }
            else{
                pPrev->right = NULL;
                printReverse(pNode->left,pPrev);
                pNode = pNode->right;
            }
        }
    }

}




int main()
{

    TreeNode* a = new TreeNode('A');
    TreeNode* b = new TreeNode('B');
    TreeNode* c = new TreeNode('C');
    TreeNode* d = new TreeNode('D');
    TreeNode* e = new TreeNode('E');
    TreeNode* f = new TreeNode('F');
    TreeNode* root = a;
    root->left = b;
    root->right = c;
    b->left = d;
    b->right = e;
    c->left = f;




    // 前序:ABDECF
    // 中序:DBEAFC
    // 後序:DEBFCA
    cout << "先序遍歷:遞歸"<< endl;
    preOrder(root);
    cout << "中序遍歷:遞歸"<< endl;
    inOrder(root);
    cout << "後序遍歷:遞歸"<< endl;
    postOrder(root);
    cout << "先序遍歷:棧"<< endl;
    preOrder2(root);
    cout << "中序遍歷:棧"<< endl;
    inOrder2(root);
    cout << "後序遍歷:雙棧"<< endl;
    postOrder2(root);
    cout << "後序遍歷:單棧"<< endl;
    postOrder3(root);
    cout << "中序遍歷:Morris"<< endl;
    inOrderMorris(root);
    cout << "後序遍歷:Morris"<< endl;
    postOrderMorris(root);


}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章