數據結構之二叉樹的遍歷


今年暑假准備復習一下數據結構與算法

今天看到二叉樹這一節,發現自己對二叉樹的遍歷的非遞歸方法還沒有掌握,因此寫個博客記錄一下

typedef struct node
{
    int value;
    struct node * left;
    struct node * right;
} *Binarytree;

首先先擺遞歸版本

前序遍歷

void r_pre_traverse(Binarytree b)
{
    if (b!=NULL)
    {
        printf("%d\n",b->value);
        pre_traverse(b->left);
        pre_traverse(b->right);
    }
    
}

其他遞歸遍歷方法只是換了個位置而已,就不放在這里占位置了

非遞歸循環版本

中序遍歷(中序遍歷比較簡單先寫這個)

首先我們來分析一下主要操作

  1. 尋找左子樹,將當前節點壓入棧,直到左子樹為空
  2. 從棧中彈出來一個節點,訪問他的值(出棧即代表訪問),然后切換到他的右子樹,將右子樹壓入棧

循環結束的情況分析

  • 節點為空,棧為空
    1. 結束循環
  • 節點不為空,棧為空
    1. 剛剛開始執行,函數還未進入循環體時
    2. 在循環過程中,棧為空即代表當前節點沒有父節點,所以此時執行到根節點,正准備切換到根節點的右子樹
    3. 也有可能在循環體的訪問最后一個右節點時出現
  • 節點為空,棧不為空
    1. 棧不為空代表有父節點,節點為空,說明該節點為st.top()的右子樹
  • 節點不為空,棧不為空
    • 這個沒什么好說的

總體的流程大概是

while(b!=nullptr||!st.empty()){//右子樹為空且棧空時結束
    while(b!=nullptr){//直到左子樹為空
        執行1;
    }
    if(!st.empty()){
        執行2;
    }
    
}

丟代碼

void inoder_traverse(Binarytree b)
{
    if (b==nullptr)
        return;
    stack<Binarytree > st;
    while (b!=nullptr||!st.empty())
    {   
        while (b!=nullptr)//節點不為空
        {
            st.push(b);//壓入當前節點
            b=b->left;//切換到左子樹
        }
        //左子樹為空,且棧不為空的情況下切換到父節點
        if(!st.empty()){
            b=st.top();
            st.pop(); 

            //訪問節點
            cout<<b->value<<endl;
            //如果有右子樹,則切換到右子樹,沒有的話為空,再次循環時也會正常,所以不需要判斷
            b=b->right;
        }
    }
    
}

前序遍歷

首先我們來分析一下主要操作

  1. 邊訪問節點邊輸出當前節點的值,並把節點存入棧中,然后訪問左子樹,直到訪問節點為空
  2. 如果棧不空,從棧中彈出節點,訪問他的右子樹

循環結束的情況分析

  • 感覺情況和中序遍歷差不多的樣子,就不列舉了

總體的流程大概是

while(b!=nullptr||!st.empty()){
    while(b!=nullptr){
        執行1;
    }
    if(!st.empty()){
        執行2;
    }
}

丟代碼

void pre_traverse(Binarytree b)
{
    if (b==nullptr)
    return;
    stack<Binarytree > st;
    while (b!=nullptr||!st.empty())
    {   
        while (b!=nullptr)//節點不為空
        {
            //訪問節點
            cout<<b->value<<endl;
            st.push(b);//壓入當前節點
            b=b->left;//切換到左子樹
        }
        //左子樹為空,且棧不為空的情況下切換到父節點
        if(!st.empty()){
            b=st.top();
            st.pop(); 
    
            //如果有右子樹,則切換到右子樹,沒有的話為空,再次循環時也會正常,所以不需要判斷
            b=b->right;
        }
    }
}

后序遍歷,這個好像有點復雜來着(有兩種思路,一種是節點增加一個參數用以確認是否第一次訪問,具體方法呢和上面兩種差不多,第二種是使用雙指針的方法,這里着重討論第二種)

具體思路

利用兩個指針 cur和pre進行操作,其中cur用來保存當前節點的指針,pre前一次訪問的節點(pre和cur沒有確定的父子關系).要保證根結點在左孩子和右孩子訪問之后才能訪問,因此對於任一結點P,先將其入棧。

  1. 如果P不存在左孩子和右孩子,則可以直接訪問它;
  2. 或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被訪問過了,則同樣可以直接訪問該結點。
  3. 若非上述兩種情況,則將P的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子前面被訪問,左孩子和右孩子都在根結點前面被訪問。

首先我們來分析一下主要操作

  1. 訪問節點 直接將節點壓入棧中
    • 如果節點不存在子樹,直接訪問
    • 如果節點存在節點,待按照順序訪問子節點后再從棧中彈出父節點進行訪問

循環結束的情況分析(循環開始前將根節點壓入)

  • 節點為空,棧為空
    1. 空樹,直接過濾
  • 節點不為空,棧為空
    1. 根節點輸出完畢,結束循環
  • 節點為空,棧不為空
    1. 右端子樹訪問完畢,
  • 節點不為空,棧不為空
    • 這個沒什么好說的

總體的流程大概是

while(!st.empty()){
    if((cur->left==nullptr&&cur->right==nullptr)||
        ((pre!=nullptr)&&(pre==cur->left||pre==cur->right))){//節點不存在子樹,或者子樹已經都被訪問過了
        執行1;
    }
    else{
        執行2;
    }
}

丟代碼

void postorder_traversal(Binarytree b)
{
    if (b==nullptr)
        return;
    Binarytree cur;//是指針哦 保存當前訪問的節點
    Binarytree pre=nullptr;
    stack<Binarytree > st;
    st.push(b);//先把根節點壓入堆棧
    while (!st.empty())//堆棧不空,堆棧空的時候表示根節點已經被訪問了
    {
        cur=st.top();//當前訪問的節點為棧頂節點 不彈出
        //節點沒有子樹
        if ((cur->left==nullptr&&cur->right==nullptr)||
        (pre!=nullptr&&(pre==cur->left||pre==cur->right)))//或者節點的子樹都被訪問過了,即上一個訪問的節點是當前節點的子節點
        {
            //訪問當前節點
            cout<<cur->value<<endl;
            st.pop();//彈出堆棧 彈出就代表訪問了該節點

            pre=cur;//為下一次訪問做准備
        }
        else//存在還未訪問的子樹,進行子樹的訪問
        {

            if (cur->left!=nullptr)
                st.push(cur->left);
            if (cur->right!=nullptr)
                st.push(cur->right);
        }
    }
}

這個算法的實現我參考了這個大佬的博客:二叉樹的非遞歸遍歷


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM