數據結構之二叉樹(遍歷、建立、深度)

轉自:http://blog.chinaunix.net/uid-26548237-id-3476141.html

1、二叉樹的深度遍歷

        二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹的所有結點,使得每個結點被訪問一次且僅被訪問一次。 

        對於二叉樹的深度遍歷,有前序遍歷二叉樹、中序遍歷二叉樹、後序遍歷二叉樹三種形式,下面分別進行學習和介紹。

1.1 二叉樹的前序遍歷

        1)前序遞歸遍歷

        規則是若二叉樹爲空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。如下圖所示,遍歷的順序爲ABDGHCEIF

 


前序遞歸遍歷的代碼實現,如下所示。

1
2
3
4
5
6
7
8
9
10
//前序遞歸遍歷
void PreOrderTraverse(BiTree t)
{
    if(t != NULL)
    {
        printf("%c ", t->data);
        PreOrderTraverse(t->lchild);
        PreOrderTraverse(t->rchild);
    }
}



        2)前序非遞歸遍歷

    根據前序遍歷訪問的順序,優先訪問根結點,然後再分別訪問左孩子和右孩子。即對任一結點,其可看做是根結點,因此可以直接訪問,訪問完之後,若其左孩子不爲空,按相同的規則訪問它的左子樹;當訪問其左子樹時,再訪問它的右子樹,因此其處理過程如下:

        對於任一結點p

        a. 訪問結點p,並將結點p入棧;

        b. 判斷結點p的左孩子是否爲空,若爲空,則取棧頂結點並進行出棧操作,並將棧頂結點的右孩子置爲當前的結點p,循環置a;若不爲空,則將p的左孩子置爲當前結點p

        c. 直到p爲空,並且棧爲空,則遍歷結束。

        前序非遞歸遍歷代碼實現如下所示。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//前序非遞歸遍歷
int NoPreOrderTraverse(BiTree t)
{
    SqStack s;
    InitStack(&s);
 
    BiTree tmp = t;
    if(tmp == NULL)
    {
        fprintf(stdout, "the tree is null.\n");
        return ERROR;
    }
 
    while((tmp != NULL) || (IsEmpty(&s) != 1))
    {
        while(tmp != NULL)
        {
            Push(&s, tmp);
            printf("%c ", tmp->data);
            tmp = tmp->lchild;
        }
        if(IsEmpty(&s) != 1)
        {
            Pop(&s, &tmp);
            tmp = tmp->rchild;
        }
    }
     
    return OK;
}


1.2 中序遍歷二叉樹

        1)中序遞歸遍歷

      規則是若樹爲空,則空操作返回,否則從根結點開始(注意這裏並不是先訪問根結點),中序遍歷根結點的左子樹,然後是訪問根結點,最後中序遍歷右子樹。如下圖所示,遍歷的順序爲:GDHBAEICF


   

    中序遞歸遍歷代碼實現如下所示。

1
2
3
4
5
6
7
8
9
10
//中序遞歸遍歷
void InOrderTraverse(BiTree t)
{
    if(t != NULL)
    {
        InOrderTraverse(t->lchild);
        printf("%c ", t->data);
        InOrderTraverse(t->rchild);
    }
}


        2)中序非遞歸遍歷

    根據中序遍歷的順序,對於任一結點,優先訪問其左孩子,而左孩子結點又可以看做一個根結點,然後繼續訪問其左孩子結點,直到遇到左孩子結點爲空的結點才停止訪問,然後按相同的規則訪問其右子樹。其處理過程如下:

       對於任一結點:

       a. 若其左孩子不爲空,則將p入棧,並將p的左孩子設置爲當前的p,然後對當前結點再進行相同的操作;

       b. 若其左孩子爲空,則取棧頂元素並進行出棧操作,訪問該棧頂結點,然後將當前的p置爲棧頂結點的右孩子;

       c. 直到p爲空並且棧爲空,則遍歷結束。

       中序非遞歸遍歷代碼實現如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//中序非遞歸遍歷二叉樹
int NoInOrderTraverse(BiTree t)
{
    SqStack s;
    InitStack(&s);
     
    BiTree tmp = t;
    if(tmp == NULL)
    {
        fprintf(stderr, "the tree is null.\n");
        return ERROR;
    }
 
    while(tmp != NULL || (IsEmpty(&s) != 1))
    {
        while(tmp != NULL)
        {
            Push(&s, tmp);
            tmp = tmp->lchild;
        }
 
        if(IsEmpty(&s) != 1)
        {
            Pop(&s, &tmp);
            printf("%c ", tmp->data);
            tmp = tmp->rchild;
        }
    }
    return OK;
}


1.3 後序遍歷二叉樹

        1)後序遞歸遍歷

       規則是若樹爲空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點。遍歷的順序爲:GHDBIEFCA


        後序遞歸遍歷代碼實現如下所示。

1
2
3
4
5
6
7
8
9
10
//後序遞歸遍歷
void PostOrderTraverse(BiTree t)
{
    if(t != NULL)
    {
        PostOrderTraverse(t->lchild);
        PostOrderTraverse(t->rchild);
        printf("%c ", t->data);
    }
}

        2)後序非遞歸遍歷

    後序遍歷的非遞歸實現是三種遍歷方式中最難的一種。因爲在後序遍歷中,要保證左孩子和右孩子都已被訪問,並且左孩子在右孩子之前訪問才能訪問根結點,這就爲流程控制帶來了難題。下面介紹一種思路。

     要保證根結點在左孩子和右孩子訪問之後才能訪問,因此對於任一結點p,先將其入棧。若p不存在左孩子和右孩子,則可以直接訪問它,或者p存在左孩子或右孩子,但是其左孩子和右孩子都已經被訪問過了,則同樣可以直接訪問該結點。若非上述兩種情況,則將p的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子之前別訪問,左孩子和右孩子都在根結點前面被訪問。

        後序非遞歸遍歷代碼實現如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//後序非遞歸遍歷二叉樹
int NoPostOrderTraverse(BiTree t)
{
    SqStack s;
    InitStack(&s);
 
    BiTree cur;     //當前結點 
    BiTree pre = NULL;      //前一次訪問的結點
    BiTree tmp;
 
    if(t == NULL)
    {
        fprintf(stderr, "the tree is null.\n");
        return ERROR;
    }
 
    Push(&s, t);
    while(IsEmpty(&s) != 1)
    {
        GetTop(&s, &cur);//
        if((cur->lchild == NULL && cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
        {
            printf("%c ", cur->data);    //如果當前結點沒有孩子結點或者孩子結點都已被訪問過
            Pop(&s, &tmp);
            pre = cur;
        }
        else
        {
            if(cur->rchild != NULL)
            {
                Push(&s, cur->rchild);
            }
            if(cur->lchild != NULL)
            {
                Push(&s, cur->lchild);
            }
        }
    }
    return OK;
}

2、二叉樹的廣度遍歷 

   廣度遍歷二叉樹(即層次遍歷)是用隊列來實現的,從二叉樹的第一層(根結點)開始,自上而下逐層遍歷;在同一層中,按照從左到右的順序對結點逐一訪問。如下圖所示,遍歷的順序爲:ABCDEFGHI

   

    按照從根結點到葉結點、從左子樹到右子樹的次序訪問二叉樹的結點,具體思路如下:

A. 初始化一個隊列,並把根結點入隊列;

B. 當隊列爲非空時,循環執行步驟3到步驟5,否則執行步驟6

C. 出隊列取得一個結點,訪問該結點;

D. 若該結點的左子樹爲非空,則將該結點的左子樹入隊列;

E. 若該結點的右子樹爲非空,則將該結點的右子樹入隊列;

F. 結束。

廣度遍歷二叉樹代碼實現,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//層次遍歷二叉樹 - 廣度遍歷二叉樹 - 隊列
int TraverseBiTree(BiTree t)
{
    LinkQueue q;
    InitQueue(&q);
     
    BiTree tmp = t;
    if(tmp == NULL)
    {
        fprintf(stderr, "the tree is null.\n");
        return ERROR;
    }
 
    InsertQueue(&q, tmp);
    while(QueueIsEmpty(&q) != OK)
    {
        DeQueue(&q, &tmp);
        printf("%c ", tmp->data);
        if(tmp->lchild != NULL)
        {
            InsertQueue(&q, tmp->lchild);
        }
        if(tmp->rchild != NULL)
        {
            InsertQueue(&q, tmp->rchild);
        }
    }
 
    return OK;
}

3、二叉樹的建立

   如果要在內存中建立一個如下左圖這樣的樹,wield能讓每個結點確認是否有左右孩子,我們對它進行擴展,變成如下右圖的樣子,也就是將二叉樹中的每個結點的空指針引出一個虛結點,其值爲一個特定值,比如#,稱之爲擴展二叉樹。擴展二叉樹就可以做到一個遍歷序列確定一棵二叉樹了。如前序遍歷序列爲AB#D##C##

    有了這樣的準備,就可以看看如何生成一棵二叉樹了。假設二叉樹的結點均爲一個字符,把剛纔前序遍歷序列AB#D##C##用鍵盤挨個輸入,實現的算法如下所示。

二叉樹建立實現代碼一,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//創建樹
//按先後次序輸入二叉樹中結點的值(一個字符),#表示空樹
//構造二叉鏈表表示的二叉樹
BiTree CreateTree(BiTree t)
{
    char ch;
    scanf("%c", &ch);
 
    if(ch == '#')
    {
        t = NULL;
    }
    else
    {
        t = (BitNode *)malloc(sizeof(BitNode));
        if(t == NULL)
        {
            fprintf(stderr, "malloc() error in CreateTree.\n");
            return;
        }
 
        t->data = ch;                        //生成根結點
        t->lchild = CreateTree(t->lchild);    //構造左子樹
        t->rchild = CreateTree(t->rchild);    //構造右子樹
    }
    return t;
}

    二叉樹建立實現代碼二,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//創建樹方法二
int CreateTree2(BiTree *t)
{
    char ch;
    scanf("%c", &ch);
 
    if(ch == '#')
    {
        (*t) = NULL;
    }
    else
    {
        (*t) = (BiTree)malloc(sizeof(BitNode));
        if((*t) == NULL)
        {
            fprintf(stderr, "malloc() error in CreateTree2.\n");
            return ERROR;
        }
 
        (*t)->data = ch;
        CreateTree2(&((*t)->lchild));
        CreateTree2(&((*t)->rchild));
    }
    return OK;
}

    其實建立二叉樹,也是利用了遞歸的原理。只不過在原來應該打印結點的地方,改成生成結點、給結點賦值的操作而已。因此,完全可以用中序或後序遍歷的方式實現二叉樹的建立,只不過代碼裏生成結點和構造左右子樹的代碼順序交互一下即可。

4、二叉樹的深度

樹中結點的最大層次稱爲樹的深度。對於二叉樹,求解樹的深度用以下兩種方法實現。即非遞歸和遞歸的方法實現。

遞歸求解二叉樹的深度實現代碼,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//二叉樹的深度 - 遞歸
//返回值: 二叉樹的深度
int BiTreeDeep(BiTree t)
{
    int dept = 0;
     
    if(t)
    {
        int lchilddept = BiTreeDeep(t->lchild);
        int rchilddept = BiTreeDeep(t->rchild);
 
        dept = lchilddept >= rchilddept ? (lchilddept + 1) : (rchilddept + 1);
    }
 
    return dept;
}

    對於非遞歸求解二叉樹的深度,這裏採用了層次遍歷的原理,通過層次遍歷,找到二叉樹的最後一個結點。然後,根據該結點,尋找其雙親結點,即找到其上一層,此時深度dept1,依次進行,直到根結點爲止。

非遞歸求解二叉樹深度的實現,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//返回二叉樹的深度 - 非遞歸  - 受層次遍歷二叉樹的影響
//返回值: 二叉樹的深度
int NoBiTreeDeep(BiTree t)
{
    LinkQueue q;
    InitQueue(&q);
 
    BiTree tmp = t;
    if(tmp == NULL)
    {
        return ERROR;
    }
 
    InsertQueue(&q, tmp);
    while(QueueIsEmpty(&q) != OK)
    {
        DeQueue(&q, &tmp);
        //printf("%c ", tmp->data);
         
        if(tmp->lchild != NULL)
        {
            InsertQueue(&q, tmp->lchild);
        }
        if(tmp->rchild != NULL)
        {
            InsertQueue(&q, tmp->rchild);
        }
    }
     
    int deep = 0;
    BiTree m = tmp;
    BiTree n = t;
    while(m != n)
    {
        InsertQueue(&q, n);
        while(QueueIsEmpty(&q) != OK)
        {
            DeQueue(&q, &tmp);
            if(m == tmp->lchild || m == tmp->rchild)
            {
                deep++;
                m = tmp;
                break;
            }
 
            if(tmp->lchild != NULL)
            {
                InsertQueue(&q, tmp->lchild);
            }
            if(tmp->rchild != NULL)
            {
                InsertQueue(&q, tmp->rchild);
            }
        }
    }
     
    return deep + 1;    //深度從1開始
}

5、參考引用

    1)《大話數據結構》

    2)嚴蔚敏老師之《數據結構》

    3)http://www.cnblogs.com/dolphin0520/archive/2011/08/25/2153720.html

    4)http://blog.chinaunix.net/uid-20788636-id-1841329.html

    5)www.google.com


發佈了2 篇原創文章 · 獲贊 6 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章