二叉樹的三種遍歷方式

  下面介紹一下,二叉樹的三種遍歷方式,其中每一種遍歷方式都有三種實現方式。算法

  節點定義:post

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

先序遍歷

            

  以上面這張圖爲例:咱們講講樹的三種遍歷方式:this

  先序遍歷:先訪問根節點,而後訪問左孩子,最後訪問右孩子。spa

  因此,上面遍歷的結果是:GEDACHS。指針

  下面,咱們來看看具體代碼實現code

  1.遞歸實現

void preOrder(TreeNode *root){
    if (root==NULL)
        return;
    cout<<root->val<<endl;
    preOrder(root->left);
    preOrder(root->right);
}

  2.使用輔助棧  

  實現思路:1.將根節點入棧
          2.每次從棧頂彈出一個節點,訪問該節點
          3.把當前節點的右孩子入棧
          4.把當前節點的左孩子入棧blog

  具體實現:遞歸

void preOrder2(TreeNode *root){
    if (root == NULL)
        return;
    stack<TreeNode*> stk; //開闢一個棧空間
    stk.push(root);
    while(!stk.empty()){
        TreeNode* pNode = stk.pop();
        cout<<pNode->val;
        if (pNode->right!=NULL)
            stk.push(pNode->right);
        if (pNode->left!=NULL)
            stk.push(pNode->left);

    }
}

  3.Morris遍歷

  Morris遍歷,常數的空間便可在O(n)時間內完成二叉樹的遍歷。class

  O(1)空間進行遍歷困難之處在於在遍歷的子結點的時候如何從新返回其父節點?二叉樹

  在Morris遍歷算法中,經過修改葉子結點的左右空指針來指向其前驅或者後繼結點來實現的。

  其本質:線索二叉樹(Threaded Binary Tree),經過利用葉子節點空的right指針,指向中序遍歷的後繼節點,從而避免了對 stack 的依賴。

  具體實現:

void preOrder(TreeNode* root){
    if (root == NULL)
        return;

    TreeNode* pNode = root;
    while(pNode != NULL){
        if (pNode->left == NULL)
        {
            cout<<pNode->val<<endl;
            pNode = pNode->right;
        }
        else{
            TreeNode* pPre = pNode->left;
            while(pPre->right != NULL && pPre->right != pNode){
                pPre = pPre->right;
            }

            if (pPre->right == NULL)
            {
                /* code */
                pPre->right = pNode;
                cout<<pNode->val<<endl;
                pNode = pNode->left;
            }
            else{
                pPre->right = NULL;
                pNode = pNode->right;
            }
        }
    }
}

中序遍歷 

  中序遍歷:先訪問左孩點,而後訪問根節點,最後訪問右孩子。

  因此,上面遍歷的結果是:DEAGHCS。

  下面,咱們來看看具體代碼實現

  1.遞歸實現

void InOrder(TreeNode *root){
    if (root==NULL)
        return;
    InOrder(root->left);
    cout<<root->val<<endl;
    InOrder(root->right);
}

  2.使用輔助棧

  實現思路:

    初始化一個二叉樹結點pNode指向根結點;

    若pNode非空,那麼就把pNode入棧,並把pNode變爲其左孩子;(直到最左邊的結點)

    若pNode爲空,彈出棧頂的結點,並訪問該結點,將pNode指向其右孩子(訪問最左邊的結點,並遍歷其右子樹)

  具體實現:

void InOrder(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.pop();
            stk.pop();
            cout<<pNode->val<<endl;
            pNode = pNode->right;
        }
    }
}

  3.Morris遍歷

  實現思路:

    1.若是當前節點pNode的左孩子爲空,那麼輸出該節點,並把該節點的右孩子做爲當前節點

    2.若是當前節點pNode的左孩子非空,那麼找出該節點在中序遍歷的前驅結點prev

      當第一次訪問該前驅結點prev時,其右孩子一定爲空,那麼就將其右孩子設置爲當前結點,以便根據這個指針返回到當前結點pNode中,並將當前結點pNode設置爲其左孩子;  

      當該前驅結點pPre的右孩子爲當前結點,那麼就輸出當前結點,並把前驅結點的右孩子設置爲空(恢復樹的結構),將當前結點更新爲當前結點的右孩子;

    3.重複以上兩步,直到當前結點爲空。

  具體實現:

void InOrder(TreeNode *root){
    if (root == NULL)
        return;

    TreeNode* pNode = root;
    while(pNode != NULL){
        if (pNode->left == NULL)
        {
            cout<<pNode->val<<endl;
            pNode = pNode->right;
        }
        else{
            TreeNode* pPre = pNode->left;
            while(pPre->right != NULL && pPre->right != pNode){
                pPre = pPre->right;
            }

            if (pPre->right == NULL)
            {
                /* code */
                pPre->right = pNode;
                pNode = pNode->left;
            }
            else{
                pPre->right = NULL;
                cout<<pNode->val<<endl;
                pNode = pNode->right;
            }
        }
    }
}

後序遍歷

  後序遍歷:先訪問左孩子,而後訪問右孩子,最後訪問根節點。

  因此,上面遍歷的結果是:DAEHSCG。

  下面,咱們來看看具體代碼實現:

  1.遞歸實現

void PostOrder(TreeNode *root){
    if (root==NULL)
        return;
    PostOrder(root->left);
    PostOrder(root->right);
    cout<<root->val<<endl;
}

  2.使用輔助棧

void postOrder(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) {  // traverse down
            if(pNode->left)
                stk.push(pNode->left);
            else if(pNode->right)
                stk.push(pNode->right);
         /* else {
                cout << pNode->val << endl;
                stk.pop();
            }
        */
        }
        else if(pNode->left == prev) {  // traverse up from left
            if(pNode->right)
                stk.push(pNode->right);
        }
    /* else if(pNode->right == prev) { // traverse up from right
                cout << pNode->val << endl;
                stk.pop();
        }
    */
        else {
            cout << pNode->val << endl;
            stk.pop();
        }
        prev = pNode;
    }
}

  雙輔助棧實現思路:  

    設置兩個棧stk, stk2;
    將根結點壓入第一個棧stk;
    彈出stk棧頂的結點,並把該結點壓入第二個棧stk2;
    將當前結點的左孩子和右孩子前後分別入棧stk;
    當全部元素都壓入stk2後,依次彈出stk2的棧頂結點,並訪問之。
    第一個棧的入棧順序是:根結點,左孩子和右孩子;因而,壓入第二個棧的順序是:根結點,右孩子和左孩子。
    所以,彈出的順序就是:左孩子,右孩子和根結點。

void PostOrder2(TreeNode *root){ //兩個棧實現
    if (root == NULL)
        return;

    stack<TreeNode*> stk,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();
    }
}

  3.Morris遍歷實現

  實現思路:

    1.先創建一個臨時結點dummy,並令其左孩子爲根結點root,將當前結點設置爲dummy;

    2.若是當前結點的左孩子爲空,則將其右孩子做爲當前結點;

    3.若是當前結點的左孩子不爲空,則找到其在中序遍歷中的前驅結點,

      -若是前驅結點的右孩子爲空,將它的右孩子設置爲當前結點,將當前結點更新爲當前結點的左孩子;

      -若是前驅結點的右孩子爲當前結點,倒序輸出從當前結點的左孩子到該前驅結點這條路徑上全部的結點。將前驅結點的右孩子設置爲空,將當前結點更新爲當前結點的右孩子。

    4.重複以上過程,直到當前結點爲空。

  具體實現:

void reverse(TreeNode* p1,TreeNode *p2){
    if (p1 == p2)
        return;
    TreeNode* x = p1;
    TreeNode* y = p1->right;

    while(true){
        TreeNode* tmp = y->right;
        y->right = x;
        x = y;
        y = tmp;
        if (x == p2)
            break;
    }
}
void printReverse(TreeNode* p1,TreeNode *p2){
    reverse(p1,p2);
    TreeNode* pNode = p2;
    while(true){
        cout<<pNode->val<<endl;
        if (pNode == p1)
            break;
        pNode = pNode->right;
    }
    reverse(p2,p1);
}
void PostOrder3(TreeNode* root){
    if(root == NULL)
        return;

    TreeNode *dummy = new TreeNode(-1);
    dummy->left = root;
    TreeNode *pNode = dummy;
    while(pNode != NULL) {
        if(pNode->left == NULL)
            pNode = pNode->right;
        else {
            TreeNode *pPrev = pNode->left;
            while(pPrev->right != NULL && pPrev->right != pNode)
                pPrev = pPrev->right;

            if(pPrev->right == NULL) {
                pPrev->right = pNode;
                pNode = pNode->left;
            }
            else {
                printReverse(pNode->left, pPrev);
                pPrev->right = NULL;
                pNode = pNode->right;
            }
        }
    }
}

總結

  上述三種遍歷方式時間複雜度和空間複雜度分析:

    1.遞歸遍歷和非遞歸遍歷  時間複雜度0(n) 空間複雜度O(n)

    2.Morris遍歷 時間複雜度0(n) 空間複雜度O(1)

相關文章
相關標籤/搜索