1. 程式人生 > 實用技巧 >[資料結構--樹] 樹的四種遍歷方式

[資料結構--樹] 樹的四種遍歷方式

一、引言
前面介紹了樹的一些基本概念,接下來將實現樹的各種操作。樹主要應用廣的還是二叉樹,因此主要實現二叉樹的各類操作,包括建立、刪除、查詢、遍歷等等。
其中樹的基本操作最重要的是遍歷,因此首先專門講解並實現樹的遍歷演算法。

二、遍歷方式
樹共有4種遍歷方式,分別為:先序遍歷、中序遍歷、後序遍歷、層序遍歷。每種遍歷演算法都有遞迴和非遞迴兩種實現方式,下面分別介紹。
1、先序遍歷
先序遍歷的遍歷順序為根->左->右,如上圖先序遍歷為3 5 1 4 7 2 6。
(1) 遞迴實現
先序遍歷遞迴實現是先遍歷根結點,其次遍歷左子樹、最後遍歷右子樹。並且在遍歷左右子樹的時候,依舊遵循根->左->右的順序。

/* 先序遍歷--遞迴實現。 */
void PreOrderTraverse_Recursive(BiTree *T)
{
    if (T != NULL) {
        display(T);  // 先訪問根結點
        PreOrderTraverse_Recursive(T->lchild);  // 再訪問左孩子
        PreOrderTraverse_Recursive(T->rchild);  // 再訪問右孩子 
    }
    
    return;
}

(2) 非遞迴
遞迴演算法,其實本質底層都是利用“棧”這個資料結構實現的非遞迴邏輯。因此現在要實現非遞迴演算法,無外乎是利用“棧”實現遞迴的底層邏輯。

A、申請一個棧。
B、當樹非空時,根結點壓棧。
C、儲存並列印棧頂,同時棧頂彈棧(彈棧前,先要進行D、E步驟)。
D、判斷當前棧頂的左孩子,若非空,則將其壓入棧中。
E、判斷當前棧頂的右孩子,若非空,則將其壓入棧中。
F、判斷棧是否為空,若非空,重複C-E步驟,直到棧空結束遞迴。

/* 先序遍歷--非遞迴實現。 */
void PreOrderTraverse_NoRecursive(BiTree *T)
{
    BiTree *stack[N];  // 順序棧 
    int top = -1;      // 棧頂指標
    BiTree *p;
    
    stack[++top] = T;  //
首先,根入棧 while (top != -1) { // 當棧非空 p = stack[top]; // 獲取棧頂 top--; // 棧頂出棧 while (p != NULL) { display(p); // 列印棧頂 if (p->rchild != NULL) { // 如果該結點有右孩子,則右孩子入棧 stack[++top] = p->rchild; } p = p->lchild; // p需要永遠指向當前結點的一個左孩子 } } return; }

2、中序遍歷
中序遍歷的遍歷順序為左->根->右,如上圖中序遍歷為1 5 4 3 2 7 6。
(1) 遞迴實現
中序遍歷遞迴實現是先遍歷左子樹,其次遍歷根結點、最後遍歷右子樹。並且在遍歷左右子樹的時候,依舊遵循左->根->右的順序。

/* 中序遍歷--遞迴實現。 */
void INOrderTraverse_Recursive(BiTree *T)
{
    if (T != NULL) {
        INOrderTraverse_Recursive(T->lchild);
        display(T);
        INOrderTraverse_Recursive(T->rchild);
    } 
    
    return;
}

(2) 非遞迴
A、申請一個棧。
B、當樹非空時,根結點壓棧。
C、當前棧頂的左孩子入棧。
D、重複C步驟,直到當前棧頂的左孩子為空。
E、儲存並列印棧頂,同時棧頂彈棧(彈棧前,先要進行F步驟)。
F、當前棧頂的右孩子入棧。
G、判斷棧是否為空,若非空,重複C-F步驟,直到棧空結束遞迴。

/* 中序遍歷--非遞迴實現。 */
void INOrderTraverse_NoRecursive(BiTree *T)
{
    BiTree *stack[N];  // 順序棧 
    int top = -1;      // 棧頂指標
    BiTree *p;
    
    stack[++top] = T;   // 首先,根入棧 
    
    while (top != -1) {  // 當棧非空 
        p = stack[top];
        while (p != NULL) {
            stack[++top] = p->lchild;  // 當前結點的左孩子入棧,沒有則入的是NULL
            p = stack[top]; 
        }
        
        top--;  // 前一個while迴圈結束時,此時的棧頂一定為NULL,將NULL彈出來
        if (top != -1) {
            p = stack[top];   // 獲取棧頂
            top--;
            display(p);
            stack[++top] = p->rchild;  // 將p的右孩子入棧 
        } 
    }
     
    return;
}

3、後序遍歷
後序遍歷的遍歷順序為左->右->根,如上圖後序遍歷為1 4 5 2 6 7 3。
(1) 遞迴實現
後序遍歷遞迴實現是先遍歷左子樹,其次遍歷右子樹、最後遍歷根結點。並且在遍歷左右子樹的時候,依舊遵循左->右->根的順序。

/* 後序遍歷--遞迴實現。 */
void PostOrderTraverse_Recursive(BiTree *T)
{
    if (T != NULL) {
        PostOrderTraverse_Recursive(T->lchild);
        PostOrderTraverse_Recursive(T->rchild);
        display(T);
    }
    
    return;
}

(2) 非遞迴
後續遍歷的非遞迴實現是最難的一個。因為在後序遍歷中,要保證左孩子和右孩子都已被訪問,並且左孩子在右孩子前訪問,右孩子訪問後才能訪問根結點,這就為流程的控制帶來了困難。
思路:對於任一結點P,將其入棧,然後沿其左子樹一直往下搜尋,直到搜尋到沒有左孩子的結點,此時該結點出現在棧頂,但是此時不能將其出棧並訪問彈出, 因為其右孩子還未被訪問,所以接下來按照相同的規則對其右子樹進行相同的處理。當訪問完其右孩子時,該結點又出現在棧頂,此時可以將其出棧並訪問。這樣就 保證了正確的訪問順序。可以看出,在這個過程中,每個結點都兩次出現在棧頂,只有在第二次出現在棧頂時,才能訪問它。因此需要多設定一個變數標識該結點是否是第一次出現在棧頂。

/* 後序遍歷--非遞迴實現。 */
void PostOrderTraverse_NoRecursive(BiTree *T)
{
    Node stack[N];   // 順序棧
    int top = -1;   // 棧頂指標
    int tag;     // 標誌位,用來標識某個結點左右孩子情況
    BiTree *p;
    Node node;
    
    p = T;

    while ((p != NULL) || (top != -1)) {
        // 為該結點入棧做準備 
        while (p != NULL) {
            node.pt = p;
            node.flag = 0;
            stack[++top] = node;
            p = p->lchild;  // 以該結點為根結點,遍歷左孩子 
        }

        node = stack[top];  // 獲取棧頂
        top--;
        
        p = node.pt;
        tag = node.flag;
        
        // 若為0,則表示該結點尚未遍歷它的右孩子
        if (tag == 0) {
            node.pt = p;
            node.flag = 1;
            stack[++top] = node;
            p = p->rchild;    // 以該結點的右孩子為根結點,重複迴圈
        } else {  // 若當前棧頂結點的tag=1,說明此結點左右子樹都遍歷完了
            display(p);
            p = NULL;
        }    
    }
     
    return;
}

4、層序遍歷
層序遍歷就比較簡單了,其遵循先上後下,並且在每一層遵循先左後右的順序,如上圖的層序遍歷為3 5 7 1 4 2 6。
層序遍歷也可以用遞迴演算法實現,但由於該遍歷方式較為簡單,因此只介紹非遞迴實現方式了,遞迴演算法反而顯得有點複雜了。
層序遍歷方式是嚴格按照順序方式遍歷的,因此很顯然需要用到“佇列”這種具有“先進先出”的順序儲存結構。直接從上而下、先左後右的順序入隊出隊即可。

/* 層次遍歷。*/
void OrderTraverse(BiTree *T)
{
    BiTree *queue[N];  // 建立一個佇列 
    BiTree *p;
    int front = 0, rear = 0;
    
    queue[rear++] = T;   // 根入隊
    
    while (front < rear) {  // front<rear時,表示佇列非空 
        p = queue[front++];  //獲取隊頭,以及隊頭出隊
        display(p);         // 列印隊頭
        
        // 依次將隊頭的左右孩子入隊。這便是層次遍歷 
        if (p->lchild != NULL) {
            queue[rear++] = p->lchild; 
        } 
        if (p->rchild != NULL) {
            queue[rear++] = p->rchild;
        }
    } 
    
    return;
}

三、完整程式碼
下面附上完整code

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

#define N 20

/* 二叉樹結構定義 */
typedef struct tagBinaryTree {
    int data;
    struct tagBinaryTree *lchild;
    struct tagBinaryTree *rchild;
} BiTree;


void display(BiTree *Tree);     /* 列印函式 */
void PreOrderTraverse_Recursive(BiTree *Tree);     /* 先序遍歷--遞迴 */
void PreOrderTraverse_NoRecursive(BiTree *Tree);   /* 先序遍歷--非遞迴 */
void INOrderTraverse_Recursive(BiTree *Tree);      /* 中序遍歷--遞迴 */
void INOrderTraverse_NoRecursive(BiTree *Tree);    /* 中序遍歷--非遞迴 */
void PostOrderTraverse_Recursive(BiTree *Tree);    /* 後序遍歷--遞迴 */
void PostOrderTraverse_NoRecursive(BiTree *Tree);  /* 後序遍歷--非遞迴 */
void OrderTraverse(BiTree *Tree);                  /* 層次遍歷 */

/* 建立二叉樹 */
/* 這裡,主要是為了講解並實現二叉樹的遍歷演算法,而不是二叉樹的基本操作如:建立、插入、查詢、刪除等等,
   因此在這裡手動建立示例中的二叉樹。關於二叉樹更為通用的建立方法後續統一實現。 
*/
void CreateBiTree(BiTree **T)
{
    /*  思考(重點): 為什麼這裡傳參的時候用的是二級指標**T,底下用(*T)? 
        為什麼不直接傳個一級指標*T,直接用T? 
    */ 
    
    /* 根結點 */
    (*T) = (BiTree *)malloc(sizeof(BiTree));
    (*T)->data = 3;

    /* 根的左孩子 */
    (*T)->lchild = (BiTree *)malloc(sizeof(BiTree));
    (*T)->lchild->data = 5;

    /* 根的右孩子 */
    (*T)->rchild = (BiTree *)malloc(sizeof(BiTree));
    (*T)->rchild->data = 7;

    /* 根的左孩子的左孩子 */
    (*T)->lchild->lchild = (BiTree *)malloc(sizeof(BiTree));
    (*T)->lchild->lchild->data = 1;
    (*T)->lchild->lchild->lchild = NULL;
    (*T)->lchild->lchild->rchild = NULL;

    /* 根的左孩子的右孩子 */
    (*T)->lchild->rchild = (BiTree *)malloc(sizeof(BiTree));
    (*T)->lchild->rchild->data = 4;
    (*T)->lchild->rchild->lchild = NULL;
    (*T)->lchild->rchild->rchild = NULL;
    
    /* 根的右孩子的左孩子 */
    (*T)->rchild->lchild = (BiTree *)malloc(sizeof(BiTree));
    (*T)->rchild->lchild->data = 2;
    (*T)->rchild->lchild->lchild = NULL;
    (*T)->rchild->lchild->rchild = NULL;
    
    /* 跟的右孩子的右孩子 */
    (*T)->rchild->rchild = (BiTree *)malloc(sizeof(BiTree));
    (*T)->rchild->rchild->data = 6;
    (*T)->rchild->rchild->lchild = NULL;
    (*T)->rchild->rchild->rchild = NULL;
    
    return;
}

/* 列印結點 */
void display(BiTree *T)
{
    printf("%d ", T->data);    
}


/********** 先序遍歷: 根->左->右 *********/
/* 遞迴實現。遞迴實現則沒次遍歷時都將左右孩子當做一棵完整的樹來看待。 */
void PreOrderTraverse_Recursive(BiTree *T)
{
    if (T != NULL) {
        display(T);  // 先訪問根結點
        PreOrderTraverse_Recursive(T->lchild);  // 再訪問左孩子
        PreOrderTraverse_Recursive(T->rchild);  // 再訪問右孩子 
    }
    
    return;
}

/* 非遞迴實現。依賴於棧 */
void PreOrderTraverse_NoRecursive(BiTree *T)
{
    BiTree *stack[N];  // 順序棧 
    int top = -1;      // 棧頂指標
    BiTree *p;
    
    stack[++top] = T;  // 首先,根入棧
    
    while (top != -1) {  // 當棧非空
        p = stack[top];  // 獲取棧頂 
        top--;   // 棧頂出棧
        
        while (p != NULL) {
            display(p);  // 列印棧頂
            
            if (p->rchild != NULL) {  // 如果該結點有右孩子,則右孩子入棧 
                stack[++top] = p->rchild;
            }
        
            p = p->lchild;   // p需要永遠指向當前結點的一個左孩子 
        }
    }
     
    return;
}


/********** 中序遍歷: 左->根->右 *********/
/* 遞迴實現。遞迴實現則沒次遍歷時都將左右孩子當做一棵完整的樹來看待。 */
void INOrderTraverse_Recursive(BiTree *T)
{
    if (T != NULL) {
        INOrderTraverse_Recursive(T->lchild);
        display(T);
        INOrderTraverse_Recursive(T->rchild);
    } 
    
    return;
}

/* 非遞迴實現。依賴於棧 */
void INOrderTraverse_NoRecursive(BiTree *T)
{
    BiTree *stack[N];  // 順序棧 
    int top = -1;      // 棧頂指標
    BiTree *p;
    
    stack[++top] = T;   // 首先,根入棧 
    
    while (top != -1) {  // 當棧非空 
        p = stack[top];
        while (p != NULL) {
            stack[++top] = p->lchild;  // 當前結點的左孩子入棧,沒有則入的是NULL
            p = stack[top]; 
        }
        
        top--;  // 前一個while迴圈結束時,此時的棧頂一定為NULL,將NULL彈出來
        if (top != -1) {
            p = stack[top];   // 獲取棧頂
            top--;
            display(p);
            stack[++top] = p->rchild;  // 將p的右孩子入棧 
        } 
    }
     
    return;
}


/********** 後序遍歷: 左->右->根 *********/
/* 遞迴實現。遞迴實現則沒次遍歷時都將左右孩子當做一棵完整的樹來看待。 */
void PostOrderTraverse_Recursive(BiTree *T)
{
    if (T != NULL) {
        PostOrderTraverse_Recursive(T->lchild);
        PostOrderTraverse_Recursive(T->rchild);
        display(T);
    }
    
    return;
}

/* 為每個結點配備相關資訊 */
typedef struct {
    BiTree *pt;
    int flag;
} Node;

/* 非遞迴實現。依賴於棧 */
void PostOrderTraverse_NoRecursive(BiTree *T)
{
    Node stack[N];   // 順序棧
    int top = -1;   // 棧頂指標
    int tag;     // 標誌位,用來標識某個結點左右孩子情況
    BiTree *p;
    Node node;
    
    p = T;

    while ((p != NULL) || (top != -1)) {
        // 為該結點入棧做準備 
        while (p != NULL) {
            node.pt = p;
            node.flag = 0;
            stack[++top] = node;
            p = p->lchild;  // 以該結點為根結點,遍歷左孩子 
        }

        node = stack[top];  // 獲取棧頂
        top--;
        
        p = node.pt;
        tag = node.flag;
        
        // 若為0,則表示該結點尚未遍歷它的右孩子
        if (tag == 0) {
            node.pt = p;
            node.flag = 1;
            stack[++top] = node;
            p = p->rchild;    // 以該結點的右孩子為根結點,重複迴圈
        } else {  // 若當前棧頂結點的tag=1,說明此結點左右子樹都遍歷完了
            display(p);
            p = NULL;
        }    
    }
     
    return;
}


/* 層次遍歷。依賴於佇列 */
void OrderTraverse(BiTree *T)
{
    BiTree *queue[N];  // 建立一個佇列 
    BiTree *p;
    int front = 0, rear = 0;
    
    queue[rear++] = T;   // 根入隊
    
    while (front < rear) {  // front<rear時,表示佇列非空 
        p = queue[front++];  //獲取隊頭,以及隊頭出隊
        display(p);         // 列印隊頭
        
        // 依次將隊頭的左右孩子入隊。這便是層次遍歷 
        if (p->lchild != NULL) {
            queue[rear++] = p->lchild; 
        } 
        if (p->rchild != NULL) {
            queue[rear++] = p->rchild;
        }
    } 
    
    return;
}

int main()
{
    BiTree *tree;
    
    /* 建立樹 */
    CreateBiTree(&tree);
    
    /***** 先序遍歷 *****/ 
    printf("先序遍歷--遞迴實現:\t");
    PreOrderTraverse_Recursive(tree);
    printf("\n先序遍歷--非遞迴實現:\t");
    PreOrderTraverse_NoRecursive(tree);
    
    /***** 中序遍歷 *****/ 
    printf("\n\n中序遍歷--遞迴實現:\t");
    INOrderTraverse_Recursive(tree);
    printf("\n中序遍歷--非遞迴實現:\t");
    INOrderTraverse_NoRecursive(tree);
    
    /***** 後序遍歷 *****/ 
    printf("\n\n後序遍歷--遞迴實現:\t");
    PostOrderTraverse_Recursive(tree);
    printf("\n後序遍歷--非遞迴實現:\t");
    PostOrderTraverse_NoRecursive(tree);
    
    /***** 層次遍歷 *****/ 
    printf("\n\n層次遍歷:  ");
    OrderTraverse(tree);
    
    return 0;
}

四、思考
上面完整程式碼的實現過程中留了個思考:即在建立一棵二叉樹的時候,create函式為什麼入參的時候,形參用的是二級指標**T,底下使用的是*T(其實**T的*T就是個一級指標)?既然用的時候是*T本質就是個一級指標,為什麼不直接傳入個一級指標*T,然後使用的時候直接T呢?
要弄明白這個問題,首先來看一段程式碼,下面程式碼想實現的功能是,main函式裡的變數a尚未賦值,呼叫create函式,給main函式裡的變數a賦個值:

// code1
#include "stdio.h"

void create(int x)
{
    x = 1;
    return;
}

int main()
{
    int a;
    
    create(a);
    return 0;
}

根本都不用編譯執行去驗證,單用眼看就知道根本不行(原因懶得解釋)。那如何實現這個功能呢?有兩種方案可以實現:
1、返回值形式

// code2
#include "stdio.h"

int create(int x)
{
    x = 1;
    return x;
}

int main()
{
    int a;
    
    a = create(a);
    return 0;
}

2、利用指標

// code3
#include "stdio.h"

void create(int *x)
{
    *x = 1;
    return;
}

int main()
{
    int a;
    
    create(&a);
    return 0;
}

有的人看了第2種方法之後會想,好,你這裡把main裡的棧變數a的地址傳到create裡,通過在create裡直接對a地址進行修改是可行的。那建立二叉樹的時候,也直接在main裡面定義一個二叉樹,然後將該樹的地址傳給create函式,傳入一級指標,直接在create裡對數的地址進行操作,不也可以達到修改main裡的樹狀態嗎,這樣不就可以直接在create裡形參傳一級指標了嗎?
是的,沒錯,這樣當然可行,不管有多少個函式,只要是對同一個地址上進行操作,理論上都是可以修改該地址上儲存的值的。注意我上面說的,是同一個地址。但問題是,在create函式裡我們使用了malloc;當在main裡面定義了Tree之後,系統就為這個Tree分配了一個地址,而malloc之後系統又會分配另一塊地址,這兩個地址絕非是同一塊地址。因此create裡對malloc的那塊地址的各種操作,並不會改變main裡面的那塊地址上的值。如下程式碼,並不能實現修改a的值:

// code4
#include "stdio.h"
#include "stdlib.h"

void create(int *x)
{
    x = (int *)malloc(sizeof(int));
    *x = 1;
    return;
}

int main()
{
    int a;
    
    create(&a);
    return 0;
}

但,是不是malloc就一定不可以?當然不是,我們依舊可以採取有返回值的方式,將這塊地址返回給main:

// code5
#include "stdio.h"
#include "stdlib.h"

int *create(int *x)
{
    x = (int *)malloc(sizeof(int));
    *x = 1;
    return x;
}

int main()
{
    int *a;
    
    a = create(a);
    return 0;
}

那又有人問了,我現在就不想帶返回值,我就想定義void型別,完了我還就想用create裡形參用的是一級指標,那行不行呢?也當然可以:

// code6
#include "stdio.h"
#include "stdlib.h"

void create(int *x)
{
    *x = 1;
    return;
}

int main()
{
    int *a;
    
    a = (int *)malloc(sizeof(int));
    create(a);
    return 0;
}

或許又有人說了,你為什麼一定要用malloc函式,能不能不要用?或者是,我不想在main裡面提前malloc好地址、create函式我就想定義為void型、形參我就想用一級指標,那行不行呢?就比如code3裡,main裡面並沒有malloc、create是void、形參是一級指標。
那可行不可行呢?當然可行!因為樹採用鏈式儲存結構時,其本質就是一個連結串列。對連結串列而言每個結點本質都是一個指標,當一個指標指向不明確時,是禁止被操作(儲存資料)的。

// code7
#include "stdio.h"
#include "stdlib.h"

typedef struct tagNode {
    int data;
    struct tagNode *next;
} Node;

void create(Node *h)
{
    h->data = 1;   // 沒問題。並且會同步修改main裡的head->data值

    Node *node;    // 宣告一個node結點 
    node->data = 2;  // 給node儲存資料。錯誤,node本質是一個指標,其指向並不明確,野指標無法被賦值。
    node->next = NULL;    
    h->next = node;  // h連結上node。

    return;
}

int main()
{
    Node head;
    
    create(&head);
    return 0;
}

上述程式碼顯然有問題,node本質就是個指標,並且還是個野指標,野指標禁止參與操作,以上程式碼和下面的程式碼無異,都是對野指標進行了操作:

// code8
#include "stdio.h"

int main()
{
    int *p;
    
    *p = 1;   // 錯誤。野指標禁止參與運算 
    return 0;
}

所以對於每個node結點,必須malloc一塊地址,使node有明確指向:

// code9
#include "stdio.h"
#include "stdlib.h"

typedef struct tagNode {
    int data;
    struct tagNode *next;
} Node;

void create(Node *h)
{
    h->data = 1;   // 沒問題。並且會同步修改main裡的head->data值

    Node *node = (Node *)malloc(sizeof(Node));    // 宣告一個node結點 
    node->data = 2;  // 給node儲存資料。 
    node->next = NULL;
    h->next = node;  // h連結上node。

    return;
}

int main()
{
    Node head;
    
    create(&head);
    printf("%d", (&head)->next->data);
    return 0;
}

上述code9程式碼就沒有任何問題。聰明的人可能已經發現了,main裡面的head沒有malloc、create是void、create形參是一級指標。同理,對於二叉樹Tree的建立而言,當然也可以這樣去操作,但注意在create裡面就不能再對Tree的根進行malloc了,否則會導致main裡面的Tree和Create裡的Tree,不是一個Tree。
而現在是希望在create裡面給Tree分配個記憶體、並且又希望這個記憶體地址和main裡面的Tree保持一致、並且又不想以返回值的形式把malloc的這塊地址給返回出去,那就只能在create的形參裡使用二級指標。
所以以上解釋了一大堆的同時也可以看出,一棵二叉樹建立是有多種方式是可行的。形參具體怎麼傳值,依賴於你的實現方式。比如,我這裡的create的完全可以使用一級指標去傳參定義:

#include "stdio.h"
#include "stdlib.h"

/* 二叉樹結構定義 */
typedef struct tagBinaryTree {
    int data;
    struct tagBinaryTree *lchild;
    struct tagBinaryTree *rchild;
} BiTree;

/* 建立二叉樹 */
void CreateBiTree(BiTree *T)
{
    /* 根結點 */
    T->data = 3;

    /* 根的左孩子 */
    T->lchild = (BiTree *)malloc(sizeof(BiTree));
    T->lchild->data = 5;

    /* 根的右孩子 */
    T->rchild = (BiTree *)malloc(sizeof(BiTree));
    T->rchild->data = 7;

    /* 根的左孩子的左孩子 */
    T->lchild->lchild = (BiTree *)malloc(sizeof(BiTree));
    T->lchild->lchild->data = 1;
    T->lchild->lchild->lchild = NULL;
    T->lchild->lchild->rchild = NULL;

    /* 根的左孩子的右孩子 */
    T->lchild->rchild = (BiTree *)malloc(sizeof(BiTree));
    T->lchild->rchild->data = 4;
    T->lchild->rchild->lchild = NULL;
    T->lchild->rchild->rchild = NULL;
    
    /* 根的右孩子的左孩子 */
    T->rchild->lchild = (BiTree *)malloc(sizeof(BiTree));
    T->rchild->lchild->data = 2;
    T->rchild->lchild->lchild = NULL;
    T->rchild->lchild->rchild = NULL;
    
    /* 跟的右孩子的右孩子 */
    T->rchild->rchild = (BiTree *)malloc(sizeof(BiTree));
    T->rchild->rchild->data = 6;
    T->rchild->rchild->lchild = NULL;
    T->rchild->rchild->rchild = NULL;
    
    return;
}

int main()
{
    BiTree tree;
    
    CreateBiTree(&tree);  /* 建立樹 */
    
    return 0;
}

五、後記
另外,因為樹型結構本身就是一種遞迴結構,所以在後續有關樹的各種操作中,都直接用遞迴方式了,就不再思考非遞迴方式是如何實現的。