1. 程式人生 > >二叉樹前序、中序、後序(遞迴 / 非遞迴)遍歷

二叉樹前序、中序、後序(遞迴 / 非遞迴)遍歷

前語

 二叉樹的遍歷是指按一定次序訪問二叉樹中的每一個結點,且每個節點僅被訪問一次。
這裡寫圖片描述

前序遍歷

 若二叉樹非空,則進行以下次序的遍歷:
  根節點—>根節點的左子樹—>根節點的右子樹
  若要遍歷左子樹和右子樹,仍然需要按照以上次序進行,所以前序遍歷也是一個遞迴定義。

(1) 遞迴的前序遍歷
//前序遞迴遍歷
void PreOrder(BinTreeNode* pRoot)
{
    //根節點為空,直接返回
    if (pRoot == NULL)
        return;

    printf("%c ", pRoot->_data);//訪問根節點
PreOrder(pRoot->_left);//訪問左子樹 PreOrder(pRoot->_right);//訪問右子樹 }
(2) 非遞迴前序遍歷

 按照前序遍歷的規則:訪問根節點後,應根據根節點的left指標進入左子樹進行遍歷,遍歷結束後在進入右子樹進行遍歷。那如果不使用遞迴演算法時,訪問根節點後,進入左子樹遍歷結束後,現在需要進入右子樹,所以需要將右子樹的結點資訊保留下來,以便我後面遍歷右子樹使用。再有觀察可以知道,根節點的右子樹是遍歷完整個左子樹後,才去訪問的,就發現了“ 先儲存,後使用 ”,與我們所熟悉棧的特性“ 後進先出 ”一致,所以我們使用棧來儲存右子樹的資訊。

分析步驟
  1. 將根節點入棧,重複以下步驟
  2. 訪問棧頂元素(根節點),然後元素出棧
  3. 將根節點的右子樹入棧儲存
  4. 將根節點的左子樹入棧儲存(這樣就再以左子樹節點為根節點往下遍歷重複)
  5. 直至節點為NULL,棧為空,遍歷結束
//非遞迴前序遍歷
void PreOrderNor(BinTreeNode* pRoot)
{
    Stack s;
    BinTreeNode* cur = NULL;
    if (pRoot == NULL)
        return;
    InitStack(&s);
    //根節點入棧
    PushStack(&
s, pRoot); //開始迴圈操作 while (!EmptyStack(&s)) { //訪問棧頂元素(根節點) cur = TopStack(&s); printf("%c ", cur->_data); //根節點出棧 PopStack(&s); //將根節點的右子樹入棧(因為最後訪問的右子樹) if (cur->_right != NULL) PushStack(&s, cur->_right); //將根節點的左子樹入棧 if (cur->_left != NULL) PushStack(&s, cur->_left); } }

中序遍歷

 若二叉樹非空,則進行以下次序的遍歷:
  根節點的左子樹—>根節點—>根節點的右子樹

(1) 遞迴的中序遍歷
void InOrder(BinTreeNode* pRoot)
{
    //根節點為空,直接返回
    if (pRoot == NULL)
        return;

    InOrder(pRoot->_left);//訪問左子樹
    printf("%c ", pRoot->_data);//訪問根節點
    InOrder(pRoot->_right);//訪問右子樹
}
(2) 非遞迴的中序遍歷

 中序遍歷的第一個節點是最左側的節點,所以需要遍歷查詢以cur為根的最左側的結點(最左側的節點無左孩子)。找到後迴圈跳出,取棧頂元素,因為沒有左孩子,所以訪問cur指向的根節點,然後遍歷以cur為根的右子樹。重複上述迴圈,直至根節點的左子樹遍歷完成後,棧為空,要進入根節點的右子樹遍歷,此時就斷定大的迴圈條件不應該和前序遍歷相同。
 因此應該再加一個條件和它是或的關係(二者至少一個成立就好),所以我們便發現比那裡完左子樹後和根節點後,雖然棧為空,但cur的指向不為空,且當二叉樹都遍歷結束後,cur為空、棧也為空,迴圈跳出,遍歷結束。

//非遞迴的中序遍歷:左子樹-->根-->右子樹
//--->中序遍歷的便利的第一個節點是最左下的結點
//因此需要找最坐下的節點,並將查詢最坐下根節點路徑上的結點壓入棧中儲存起來
void InOrderNor(BinTreeNode* pRoot)
{
    Stack s;
    BinTreeNode* cur = NULL;
    if (pRoot == NULL)
        return;
    InitStack(&s);
    cur = pRoot;
    while (!EmptyStack(&s) || cur)
    {
        //1.找最左下的結點-->需要將所經過路徑的結點壓入棧中儲存-->最左下的根節點沒有左孩子
        while (cur)
        {
            PushStack(&s, cur);
            cur = cur->_left;
        }
        //2.取棧頂元素,遍歷以cur為根的節點
        cur = TopStack(&s);
        PopStack(&s);
        //遍歷以cur為根的二叉樹的根節點
        printf("%c ", cur->_data);
        //遍歷以cur為根的二叉樹的左節點
        cur = cur->_right;
    }
}

後序遍歷

 若二叉樹非空,則進行以下次序的遍歷:
  根節點的左子樹—>根節點的右子樹—>根節點

(1) 遞迴的後序遍歷
//遞迴的後序遍歷
void PostOrder(BinTreeNode* pRoot)
{
    if (pRoot == NULL)
        return;

    PostOrder(pRoot->_left);
    PostOrder(pRoot->_right);
    printf("%c ", pRoot->_data);
}
(2) 非遞迴的後序遍歷

 後序遍歷的第一個節點也是最左側的節點(左孩子為空)。要先找到最左側的元素並儲存其所經路徑的所有節點,以便於後面的遍歷。
 只有在遍歷完左子樹和右子樹後,才能訪問根節點。所以如何判斷是夠遍歷完左右子樹成為關鍵。因此我們會想到設定一個標誌pFlag指向最近的被遍歷的節點。當棧頂指標指向的元素的右子樹(因為相對於左右子樹來說,只要訪問過右子樹,左子樹一定已經訪問過了)與標記指標相同時,該節點已經被遍歷。

//非遞迴的後序遍歷:左子樹-->右子樹-->根節點
void PostOrderNor(BinTreeNode* pRoot)
{
    BinTreeNode* cur = pRoot;
    BinTreeNode* pFlag = NULL;
    BinTreeNode* pTop = NULL;
    Stack s;
    if (pRoot == NULL)
        return;
    InitStack(&s);

    while (cur || !EmptyStack(&s))
    {
        //仍然是找最左下的元素(無左孩子),並儲存其所經路徑中最左側的結點(入棧)
        while (cur)
        {
            PushStack(&s, cur);
            cur = cur->_left;
        }
        //pTop為根的二叉樹的根節點,根節點不能直接遍歷,除非pTop的右子樹為空
        pTop = TopStack(&s);
        //如果它沒有右孩子或者節點已遍歷過了-->元素出棧,直接訪問其根節點-->返回上一層
        //因為返回上一層後,會繼續遍歷其左右子樹,會發生死迴圈,所以需要標記其最近訪問的一個元素
        if (pTop->_right == NULL || pTop->_right == pFlag)
        {
            PopStack(&s);

            printf("%c ", pTop->_data);
            pFlag = pTop;
        }
        else  //如果它有右孩子-->以其右孩子為根節點重複以上步驟
            cur = pTop->_right;
    }
}

標頭檔案、測試函式、程式碼執行結果

//BinTree.h
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

typedef char DataType;

typedef struct BinTreeNode
{
    struct BinTreeNode* _left;  //當前節點的左子樹
    struct BinTreeNode* _right; //當前節點的右子樹
    DataType _data; //當前節點的資料域
}BinTreeNode;


//test.c
void test()
{
    BinTreeNode* pRoot = NULL;
    char* str = "ABD###CE##F";
    CreateBinTree(&pRoot, str, strlen(str), '#');
    printf("遞迴前序遍歷為:");
    PreOrderBinTree(pRoot);
    printf("\n非遞迴前序遍歷為:");
    PreOrderNor(pRoot);
    printf("\n遞迴中序遍歷為:");
    InOrderBinTree(pRoot);
    printf("\n非遞迴中序遍歷為:");
    InOrderNor(pRoot);
    printf("\n遞迴後序遍歷為:");
    PostOrderBinTree(pRoot);
    printf("\n非遞迴後序遍歷為:");
    PostOrderNor(pRoot);
    printf("\n\n");
}

這裡寫圖片描述