有根樹相關知識總結之遍歷二叉樹(C語言描述)

前言:二叉樹的遍歷方法主要有前序遍歷、中序遍歷、後續遍歷和層序遍歷。各算法又可分爲遞歸算法和非遞歸算法兩種,本文將進行區分討論。從本質上來說,二叉樹的深度優先搜索包括了前序遍歷、中序遍歷和後續遍歷;廣度優先搜索即是層序遍歷。
1. 二叉樹的數據結構定義(C語言版)

typedef struct BiNode
{
    char m_data;//數據域
    struct BiNode *plChild;//左孩子節點
    struct BiNode *prChild;//右孩子節點
}BiNode,*BiTree;

2.二叉樹的前序遍歷(遞歸版本)

算法思想:

因爲遍歷過程對每個節點都會訪問一次,包括那些爲空的節點。遞歸算法主要需要考慮遞歸出口的問題,如果二叉樹節點爲NULL(如最左下節點的左孩子即爲空),則return即可;

源碼描述:

void PreOrderWalkTree(BiNode *pRoot)
{
    if (pRoot==NULL) //遞歸出口,節點爲NULL,則返回
        return; 
    else
    {
        printf("%c",pRoot->m_data);//遍歷根節點
        PreOrderWalkTree(pRoot->plChild);//遍歷左子樹
        PreOrderWalkTree(pRoot->prChild);//遍歷右子樹
    }
    printf("\n");
}

3.二叉樹的前序遍歷(非遞歸版本)、

算法思想:

前序遍歷即對每個節點首先將訪問根節點,然後訪問左子樹,最後訪問右子樹。由於對於每個節點都遵循這樣的順序,最後訪問右子樹,所以必須先將根節點保存,即入棧;由於最先訪問根節點,所以在入棧的時候就可以訪問它。訪問完左子樹,即可出棧並獲取其右子樹,進而對右子樹進行訪問。

源碼描述:

void PreOrderWalkTree(BiNode *pRoot)
{
    BiNodeStack NodeStack;
    InitStack(&NodeStack);//創建節點類型的棧

    BiNode *pCurNode=pRoot;//當前節點
    //1. 進入主循環
    while (pCurNode!=NULL||!IsStackEmpty(&NodeStack))
    {
        while (pCurNode)
        {
            Visit(pCurNode);//訪問當前節點
            PushElement(&NodeStack,pCurNode);//將當前節點入棧
            pCurNode=pCurNode->plChild;//沿着一條路走到最左下節點,即碰到左孩子爲空
        }

        if (!IsStackEmpty(&NodeStack))
        {
            pCurNode=PopElement(&NodeStack);//碰到左孩子爲空就要出棧,此時棧頂元素是當前節點
            pCurNode=pCurNode->prChild;
        }
    }
}

4.二叉樹的中序遍歷(遞歸版本)
算法思想:

因爲遍歷過程對每個節點都會訪問一次,包括那些爲空的節點。遞歸算法主要需要考慮遞歸出口的問題,如果二叉樹節點爲NULL(如最左下節點的左孩子即爲空),則return即可;

源碼描述:

void InOrderWalkTree(BiNode *pRoot)
{
    if (pRoot==NULL) //遞歸出口,節點爲NULL,則返回
        return; 
    else
    {
        PreOrderWalkTree(pRoot->plChild);//遍歷左子樹
        printf("%c",pRoot->m_data);//遍歷根節點
        PreOrderWalkTree(pRoot->prChild);//遍歷右子樹
    }
    printf("\n");
}

5.二叉樹的中序遍歷(非遞歸版本)

算法思想

中序遍歷是先訪問節點的左子樹,然後訪問根節點,再訪問右子樹。和前序遍歷類似,由於右子樹是最後訪問,所以必須要保存根節點以在左子樹和根節點都訪問完了之後獲取右子樹進而對其訪問。具體的說,需要將根節點沿着左子樹路徑入棧,碰到左孩子爲空–>出棧–>訪問根節點–>獲取右孩子。

源碼描述:

void InOderWalkTree(BiNode *pRoot)
{
    BiNodeStack NodeStack;
    InitStack(&NodeStack);//創建節點類型的棧

    BiNode *pCurNode=pRoot;
    while (pCurNode!=NULL||!IsStackEmpty(&NodeStack))
    {
        while (pCurNode)
        {
            PushElement(&NodeStack,pCurNode);//當前節點入棧
            pCurNode=pCurNode->plChild;
        }//直到碰到左孩子爲空爲止

        if (!IsStackEmpty(&NodeStack))
        {
            pCurNode=PopElement(&NodeStack);
            Visit(pCurNode);//出棧的時候訪問節點
            pCurNode=pCurNode->prChild;//處理右孩子
        }
    }
}

6.二叉樹的後序遍歷(遞歸版本)

算法思想:

因爲遍歷過程對每個節點都會訪問一次,包括那些爲空的節點。遞歸算法主要需要考慮遞歸出口的問題,如果二叉樹節點爲NULL(如最左下節點的左孩子即爲空),則return即可;

源碼描述:

void PostOrderWalkTree(BiNode *pRoot)
{
    if (pRoot==NULL) //遞歸出口,節點爲NULL,則返回
        return; 
    else
    {
        PreOrderWalkTree(pRoot->plChild);//遍歷左子樹
        PreOrderWalkTree(pRoot->prChild);//遍歷右子樹
        printf("%c",pRoot->m_data);//遍歷根節點
    }
    printf("\n");
}

7.二叉樹的後序遍歷(非遞歸版本)

算法思想

前序遍歷和中序都是最後訪問右子樹所以需要藉助棧保存根節點,後序遍歷是先訪問左子樹,其次訪問右子樹,最後訪問根節點。由於是先左子樹後右子樹,所以仍然需要保存根節點以獲取右子樹。另一方面,當左子樹爲空時,此時根節點位於棧頂,需要判斷右子樹是否已經被訪問過,如果右子樹沒有被訪問,則根節點不出棧,繼續訪問右子樹;如果右子樹已經被訪問,則根節點出棧,訪問根節點。所以,根節點有兩次位於棧頂,第二次時纔出棧。

源碼描述:

void PostOderWalkTree(BiNode *pRoot)
{
    BiNodeStack NodeStack;
    InitStack(&NodeStack);//創建節點類型的棧
    int i=0;
    bool brChlidNotProced[MaxStackSize];

    BiNode *pCurNode=pRoot;
    while (pCurNode!=NULL||!IsStackEmpty(&NodeStack))
    {
        while (pCurNode)
        {
            PushElement(&NodeStack,pCurNode);//當前節點入棧
            brChlidNotProced[i++]=false;//表示右孩子還沒有處理
            pCurNode=pCurNode->plChild;
        }//直到碰到左孩子爲空爲止

        if (!brChlidNotProced[i-1]) //右孩子沒有被處理
        {
            pCurNode=GetStackTop(&NodeStack);//獲取棧頂元素,未出棧
            pCurNode=pCurNode->prChild;
            brChlidNotProced[i-1]=true;
        }
        else
        {
            pCurNode=PopElement(&NodeStack);//右子樹已經處理,可以出棧
            Visit(pCurNode);//訪問
            i--;
            pCurNode=NULL;//棧頂元素彈出並訪問後,還要進行一次出棧操作
        }
    }
}

8.二叉樹的層序遍歷(非遞歸版本)

算法思想:

由於對於每一層都需要從左向右遍歷節點,因此有“先入先出”的意思,採用隊列進行描述。首先將根節點入隊列,其次進入主循環,當前節點出隊列並訪問,如果其左孩子不爲空,則左孩子入隊列,如果右孩子不爲空,則右孩子入隊列。

源碼描述:

void LayerOderWalkTree(BiNode *pRoot)
{
    BiNodeQueue NodeQueue;
    InitQueue(&NodeQueue);//初始化隊列

    BiNode *pCurNode=pRoot;
    EnQueue(&NodeQueue,pCurNode);//根節點入隊列

    //進入主循環,直至隊列爲空
    while (!IsQueueEmpty(&NodeQueue))
    {
        pCurNode=DeQueue(&NodeQueue);
        Visit(pCurNode);//訪問當前節點

        if (pCurNode->plChild!=NULL) //左孩子不爲空,則進隊列
        {
            EnQueue(&NodeQueue,pCurNode->plChild);
        }

        if (pCurNode->prChild!=NULL) //右孩子不爲空,則進隊列
        {
            EnQueue(&NodeQueue,pCurNode->prChild);
        }
    }
}

總結:以上四種遍歷算法的實現均採用教科書上的思想,即:

  1. 前序遍歷:當前節點是在入棧的時候被訪問;
  2. 中序遍歷:當前節點是在出棧的時候被訪問;
  3. 後續遍歷:當前節點是在第二次在棧頂的時候被訪問;有兩次出現的棧頂,第一次是右子樹未處理的時候,第二次是右子樹處理完了的時候。因此需要標記位bFlag用於標識右子樹是否被處理:
    3.1 bFlag=1,右子樹未處理->不出棧,處理右子樹;
    3.2 bFlag=0,右子樹已處理->出棧,訪問當前節點;
  4. 層序遍歷:當前節點出隊列的時候被訪問;同時將其不爲空的孩子節點入隊列。

樹的深度優先搜索和廣度優先搜索:

  • 樹的深度優先搜索是指首先從樹的根節點出發,然後選擇一個與其相鄰的且未被訪問的頂點(孩子節點),依次進行,直到某個節點與其相鄰的所有節點都已經被訪問或者爲空爲止。然後退回到已經被訪問的頂點序列中最後一個擁有未被訪問的鄰接節點的節點。直至所有節點均已經被訪問。

  • 樹的廣度優先搜索是指首先從樹的根節點出發,遍歷與其相鄰的所有節點,然後再遍歷這些鄰接節點的鄰接節點,依次進行下去,直至所有節點均已經被訪問。


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