資料結構之二叉樹(遍歷、建立、深度)
1、二叉樹的深度遍歷
二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹的所有結點,使得每個結點被訪問一次且僅被訪問一次。訪問和次序。
對於二叉樹的深度遍歷,有前序遍歷二叉樹、中序遍歷二叉樹、後序遍歷二叉樹三種形式,下面分別進行學習和介紹。
1.1 二叉樹的前序遍歷
1)前序遞迴遍歷
規則是若二叉樹為空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。如下圖所示,遍歷的順序為ABDGHCEIF。
前序遞迴遍歷的程式碼實現,如下所示。
1 2 3 4 5 6 7 8 9 10 |
//前序遞迴遍歷 void { if(t != NULL) { printf("%c ", t->data); PreOrderTraverse(t->lchild); PreOrderTraverse(t->rchild); } } |
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); } } |
1.3 後序遍歷二叉樹
1)後序遞迴遍歷
規則是若樹為空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點。遍歷的順序為:
後序遞迴遍歷程式碼實現,如下所示。
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、二叉樹的廣度遍歷
廣度遍歷二叉樹(即層次遍歷)是用佇列來實現的,從二叉樹的第一層(根結點)開始,自上而下逐層遍歷;在同一層中,按照從左到右的順序對結點逐一訪問。如下圖所示,遍歷的順序為: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、推導遍歷結果
兩個二叉樹遍歷的性質:
前序遍歷和中序遍歷,可以唯一確定一棵樹。
後續遍歷和中序遍歷,可以唯一確定一棵樹。(這一塊多做練習即可)
4、二叉樹的建立
如果要在記憶體中建立一個如下左圖這樣的樹,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; } |
其實建立二叉樹,也是利用了遞迴的原理。只不過在原來應該列印結點的地方,改成生成結點、給結點賦值的操作而已。因此,完全可以用中序或後序遍歷的方式實現二叉樹的建立,只不過程式碼裡生成結點和構造左右子樹的程式碼順序互動一下即可。
5、二叉樹的深度
樹中結點的最大層次稱為樹的深度。對於二叉樹,求解樹的深度用以下兩種方法實現。即非遞迴和遞迴的方法實現。求二叉樹的深度也是非常常見的一個操作。這個操作使用後續遍歷比較符合人們求解二叉樹高度的思維方式:首先分別求出左右子樹的高度,在此基礎上得出該棵樹的高度,即左右子樹較大的高度值加1.
遞迴求解二叉樹的深度實現程式碼,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//二叉樹的深度 - 遞迴 //返回值: 二叉樹的深度 int tree_height(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; } |
6、統計二叉樹中的葉子節點
二叉樹的遍歷是操作二叉樹的基礎,二叉樹的很多特性都可以通過遍歷二叉樹得到。統計二叉樹葉子節點的個數常見的操作。
/*統計二叉樹中的葉子結點數*/
/**************************************************
演算法描述:編寫遞迴演算法,計算二叉樹中葉子節點數目
****************************************************/
int leaf_num(ptnode list)
{
if(NULL == list)
{
return 0; //空樹,無葉子
}
else if(!list->pLchild && !list->pRchild)
{
return 1;
}
else
{
return (leaf(list->pLchild) + leaf(list->pRchild));
}
}
/************************
演算法分析:
首先要明白其本質還是二叉樹的遍歷.只不過是帶額外有條件的輸出.即找出葉子結點並
進行計數.
1.提到計數的話,一開始的反應就是建立一個int型變數(如count),然後找到一個符合條件
的就進行count ++; 但在這裡就不是那麼合適了.因為若是要遍歷,選擇遞迴遍歷,則每次
呼叫一次遞迴函式都會建立一個count,各個count都不相同.而且都會被初始化為0,這樣就
沒什麼意義了.
2.所以採取的方法就是利用函式返回值.把函式定義為返回值為int型的函式.
3.然後進行判斷:
if(!t->lch && !t->rch)如果左右結點都為NULL,則返回1(也就是計數+1).
否則就呼叫遞迴函式,先左子樹,後右子樹.這個演算法真正精髓的一句就是:
return (leaf(t->lch) + leaf(t->rch));
在呼叫遞迴的同時把各個遞迴函式的返回值都加了起來.而最終返回到主函式的值
就是葉子節點的個數!巧妙!
**************************/
如果需要完整程式,可找我索取。
出自:《大話資料結構》嚴蔚敏老師之《資料結構》
二叉樹的演算法我大都是利用遞迴實現的,非遞迴的後面用到再進行深究。二叉樹自己還不是十分理解,以後還應該進行加強鞏固。