1. 程式人生 > 實用技巧 >資料結構實驗(二)

資料結構實驗(二)

資料結構實驗(二):

二叉樹的建立與遍歷

一、實驗目的

(1)熟練掌握二叉樹的結構特徵,以及各種儲存結構的特點及適用範圍。

(2)掌握在二叉連結串列儲存結構中的常用遍歷方法:先序遞迴遍歷、中序遞迴遍歷、後序遞迴遍歷、中序遍歷非遞迴演算法;瞭解二叉樹的層序遍歷。

(3)瞭解二叉樹遍歷演算法的簡單應用。

二、實驗內容

1.問題描述

已知二叉樹,如圖所示,基於圖示二叉樹程式設計實現以下演算法:

(1) 建立二叉樹,以先序次序輸入二叉樹序列建立二叉樹;

(2) 採用先序遍歷的遞迴演算法遍歷二叉樹,並輸出先序序列;

(3) 採用中序遍歷的遞迴和非遞迴演算法遍歷二叉樹,並輸出中序序列;

(4) 採用後序遍歷的遞迴演算法遍歷二叉樹,並輸出後序序列;

(5) 計算二叉樹的高度並輸出;

(6) 計算二叉樹中所有結點個數並輸出;

(7) 計算二叉樹中葉子結點個數並輸出。

2.實驗要求

(1)設計二叉樹的鏈式儲存結構;

(2)用先序建立的方式建立二叉樹;

(3)每完成一個演算法,及時輸出結果,便於觀察操作結果;

(4)設計測試用例,測試程式的正確性。

3.測試用例

序號輸入輸出說明
1. ABC@@DE@G@@F@@@ 先序遍歷:ABCDEGF 中序遍歷(遞迴):CBEGDFA 中序遍歷(非遞迴):CBEGDFA 後序遍歷:CGEFDBA 該二叉樹的高度為: 5 該二叉樹中所有結點個數為: 7 該二叉樹中葉子結點個數為: 3
2.
ABD@@EG@@@C@F@@ 先序遍歷:ABDEGCF 中序遍歷(遞迴):DBGEACF 中序遍歷(非遞迴):DBGEACF 後序遍歷:DGEBFCA 該二叉樹的高度為: 4 該二叉樹中所有結點個數為: 7 該二叉樹中葉子結點個數為: 3
3. @ 空樹

三、資料結構及相關函式說明

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3   4 typedef struct TNode * BinTree;   /* 二叉樹型別 */
  5 typedef char ElementType;
  6   7
8 struct TNode{ /* 樹結點定義 */ 9 ElementType Data; /* 結點資料 */ 10 BinTree Left; /* 指向左子樹 */ 11 BinTree Right; /* 指向右子樹 */ 12 }; 13 typedef int Position; 14 typedef struct SNode * PtrToSNode; 15 struct SNode{ 16 BinTree* Data; 17 int Top; 18 int m; 19 }; 20 typedef PtrToSNode Stack; 21 22 typedef struct QNode* PtrToQNode; 23 struct QNode{ 24 BinTree* Date; 25 Position Front,Rear; 26 int Max_s; 27 }; 28 typedef PtrToQNode Queue; 29 30 31 Stack CreateStack(int MaxSize){ 32 Stack S = (Stack)malloc(MaxSize * sizeof(struct SNode)); 33 S->Data = (BinTree*)malloc(MaxSize * sizeof(struct TNode)); 34 S->Top = -1; 35 S->m = MaxSize; 36 return S; 37 } 38 39 Queue CreateQueue(int Max_s){ 40 Queue Q = (Queue)malloc(sizeof(struct QNode)); 41 Q->Date = (BinTree*)malloc(Max_s*sizeof(struct QNode)); 42 Q->Front = Q->Rear = 0; 43 Q->Max_s = Max_s; 44 return Q; 45 } 46 47 bool IsFull(Stack S){ 48 return (S->Top == S->m - 1); 49 } 50 51 bool IsFull2(Queue Q){ 52 return (Q->Rear+1)%(Q->Max_s) == Q->Front; 53 } 54 55 bool IsEmpty(Stack S){ 56 return(S->Top == -1); 57 } 58 59 bool IsEmpty2(Queue Q){ 60 return Q->Front == Q->Rear; 61 } 62 63 bool AddQ(Queue Q ,BinTree X){//無頭結點鏈式儲存 64 if(IsFull2(Q)){ 65 printf("佇列滿"); 66 return false; 67 } 68 else{ 69 Q->Rear = (Q->Rear+1)%(Q->Max_s); 70 Q->Date[Q->Rear] = X; 71 return true; 72 } 73 } 74 75 BinTree DeleteQ(Queue Q){ 76 if (IsEmpty2(Q)){ 77 printf("佇列空"); 78 return NULL; 79 } 80 else{ 81 Q->Front = (Q->Front+1)%Q->Max_s; 82 return Q->Date[Q->Front]; 83 } 84 } 85 86 bool Push (Stack S,BinTree X){ 87 if(IsFull(S)){ 88 printf("堆疊滿"); 89 return false; 90 91 } 92 else { 93 S->Data[++(S->Top)] = X; 94 return true; 95 } 96 } 97 98 BinTree Pop(Stack S){ 99 if(IsEmpty(S)){ 100 printf("堆疊空"); 101 return NULL; 102 } 103 else{ 104 return (S->Data[(S->Top--)]); 105 } 106 } 107 108 //按先序次序輸入二叉樹中結點的值(一個字元),@表示空樹,構造二叉連結串列表示二叉樹T 109 BinTree CreateBinTree() 110 { 111 ElementType ch; 112 BinTree T; 113 scanf("%c",&ch); 114 if(ch == '@') 115 T = NULL; 116 else { 117 T = (BinTree)malloc(sizeof(struct TNode)); 118 T->Data = ch; 119 T->Left = CreateBinTree(); 120 T->Right = CreateBinTree(); 121 } 122 return T; 123 } 124 125 //先序遍歷 126 void PreorderTraversal(BinTree BT){ 127 if (BT){ 128 printf("%c",BT->Data); 129 PreorderTraversal(BT->Left); 130 PreorderTraversal(BT->Right); 131 } 132 } 133 134 //中序遍歷 135 void InorderTraversal(BinTree BT){ 136 if(BT){ 137 InorderTraversal(BT->Left); 138 printf("%c",BT->Data); 139 InorderTraversal(BT->Right); 140 } 141 } 142 143 //後序遍歷 144 void PostorderTraversal(BinTree BT){ 145 if(BT){ 146 PostorderTraversal(BT->Left); 147 PostorderTraversal(BT->Right); 148 printf("%c",BT->Data); 149 } 150 } 151 152 //中序遍歷非遞迴 153 void InorderTraversal2(BinTree BT){ 154 BinTree T; 155 Stack S = CreateStack(100); 156 T = BT; 157 while(T||!IsEmpty(S)){ 158 while (T){ 159 Push(S,T); 160 T = T->Left; 161 } 162 } 163 T = Pop(S); 164 printf("%c",T->Data); 165 T = T->Right; 166 } 167 168 //輸出二叉樹的高度 169 int GetHeight(BinTree BT){ 170 int HL,HR,MaxH; 171 if(BT){ 172 HL = GetHeight(BT->Left); 173 HR = GetHeight(BT->Right); 174 MaxH = HL>HR?HL:HR; 175 return MaxH+1; 176 } 177 else return 0; 178 } 179 180 //層序遍歷計算結點個數 181 int NodeCount(BinTree BT){ 182 int Count = 0; 183 Queue Q; 184 BinTree T; 185 if(!BT){ 186 return 0; 187 } 188 Q = CreateQueue(100); 189 AddQ(Q,BT); 190 while(!IsEmpty2(Q)){ 191 T = DeleteQ(Q); 192 Count++; 193 /* printf("%d",T->Data); */ 194 if(T->Left) AddQ(Q,T->Left); 195 if(T->Right) AddQ(Q,T->Right); 196 } 197 return Count; 198 } 199 200 //輸出所有葉節點 201 int leafnum = 0; 202 int PreOrderPrintLeaves(BinTree BT){ 203 204 if(BT){ 205 if(!BT->Left&&!BT->Right){ 206 leafnum ++; 207 } 208 PreOrderPrintLeaves(BT->Left); 209 PreOrderPrintLeaves(BT->Right); 210 } 211 return leafnum; 212 } 213 int main() 214 { 215 BinTree BT; 216 int height,count,leafnum; 217 printf("請按先序次序輸入樹的結點,空樹輸入@: "); 218 BT = CreateBinTree(); 219 220 if(BT == NULL){ 221 printf("\n空樹!\n"); 222 }else{ 223 printf("先序遍歷的結果為: "); 224 PreorderTraversal(BT); 225 printf("\n"); 226 227 printf("中序遍歷的結果為(遞迴): "); 228 InorderTraversal(BT); 229 printf("\n"); 230 231 printf("中序遍歷的結果為(非遞迴): "); 232 InorderTraversal(BT); 233 printf("\n"); 234 235 printf("後序遍歷的結果為: "); 236 PostorderTraversal(BT); 237 printf("\n"); 238 239 height = GetHeight(BT); 240 printf("該二叉樹的高度為: %d",height); 241 printf("\n"); 242 243 count = NodeCount(BT); 244 printf("該二叉樹中所有結點個數為: %d",count); 245 printf("\n"); 246 leafnum = PreOrderPrintLeaves(BT) ; 247 printf("該二叉樹中葉子結點個數為: %d",leafnum); 248 printf("\n"); 249 return 0; 250 } 251 }


四、實驗結果抓圖

五、程式碼分析

(1)結構體分析

 1 typedef struct TNode * BinTree;   /* 二叉樹型別 */
 2 typedef char ElementType;
 3 struct TNode{    /* 樹結點定義 */ 
 4     ElementType Data;  /* 結點資料 */
 5     BinTree Left;   /* 指向左子樹 */ 
 6     BinTree Right;   /* 指向右子樹 */ 
 7 }; 
 8 typedef int Position;
 9 10 typedef struct SNode * PtrToSNode;
11 struct SNode{
12     BinTree* Data;
13     int Top;
14     int m;
15 };
16 typedef PtrToSNode Stack;
17 18 typedef struct QNode* PtrToQNode;
19 struct QNode{
20     BinTree* Date;
21     Position Front,Rear;
22     int Max_s;
23 };  
24 typedef PtrToQNode Queue;


定義了三個結構體TNode,SNode以及QNode;以及結構體指標。TNode結構體用來表示樹節點,SNode結構體用來表示堆疊,Top指標為棧頂指標,Data儲存元素的陣列,m表示堆疊的最大容量。QNode結構體用來表示佇列,Data儲存元素的陣列,Front,Rear佇列的頭尾指標,Max_s佇列的最大容量。

(2)主函式分析

 1 int main()
 2 {
 3     BinTree BT;
 4     int height,count,leafnum;
 5     printf("請按先序次序輸入樹的結點,空樹輸入@: ");
 6     BT = CreateBinTree();
 7     
 8     if(BT == NULL){
 9         printf("\n空樹!\n"); 
10     }else{
11         printf("先序遍歷的結果為: "); 
12         PreorderTraversal(BT);
13         printf("\n"); 
14         
15         printf("中序遍歷的結果為(遞迴): "); 
16         InorderTraversal(BT);
17         printf("\n"); 
18         
19         printf("中序遍歷的結果為(非遞迴): "); 
20         InorderTraversal(BT);
21         printf("\n"); 
22         
23         printf("後序遍歷的結果為: "); 
24         PostorderTraversal(BT);
25         printf("\n"); 
26         
27         height = GetHeight(BT);
28         printf("該二叉樹的高度為: %d",height); 
29         printf("\n");
30         
31         count = NodeCount(BT);
32         printf("該二叉樹中所有結點個數為: %d",count); 
33         printf("\n");       
34         
35         leafnum = PreOrderPrintLeaves(BT) ;
36         printf("該二叉樹中葉子結點個數為: %d",leafnum); 
37         printf("\n");
38         return 0;
39     } 
40 }
41

主函式其實就是所有程式碼的整合,也是解決實際問題的整體思路,在我看來,這是主函式最重要的一個作用。

首先,用函式CreateBinTree()建立二叉樹賦值給BT,做判斷,如果二叉樹為空,則輸出空樹,反之,對其進行先序,中序遞迴,中序非遞迴,後續演算法,以及高度,結點,以及葉子節點的計算,最後輸出。

這裡要注意的是堆疊是先進後出,佇列是先進先出,它們的不同在於堆疊相當於一條鏈,一端閉塞,進出只在另一端進行;佇列類似於圓環,從一般尾端進入,頭端取出。

(3)自定義函式分析

1.建立堆疊,以及佇列函式CreateStack(),CreateQueue();

 1 Stack CreateStack(int MaxSize){
 2     Stack S = (Stack)malloc(MaxSize * sizeof(struct SNode));
 3     S->Data = (BinTree*)malloc(MaxSize * sizeof(struct TNode));
 4     S->Top = -1;
 5     S->m = MaxSize;
 6     return S;
 7 }
 8  9 Queue CreateQueue(int Max_s){
10     Queue Q = (Queue)malloc(sizeof(struct QNode));
11     Q->Date = (BinTree*)malloc(Max_s*sizeof(struct QNode));
12     Q->Front = Q->Rear = 0;
13     Q->Max_s = Max_s;
14     return Q;
15 }

Stack在這裡是一個SNode(堆疊)結構體指標,分別給堆疊S,堆疊陣列元素Data分配連續空間,一維陣列Data[m],m的取值在[0-m-1],因此棧頂指標Top為-1是Data[Top]不存在,為空棧。佇列同理,Q->Front = Q->Rear = 0即佇列的頭尾指標在一起,入隊時,頭指標不變,尾指標向後移一位。

2.判斷空,滿函式

 1 bool IsFull(Stack S){
 2     return (S->Top == S->m - 1);
 3 }
 4  5 bool IsFull2(Queue Q){
 6     return (Q->Rear+1)%(Q->Max_s) == Q->Front;
 7 }
 8  9 bool IsEmpty(Stack S){
10     return(S->Top == -1);
11 }
12 13 bool IsEmpty2(Queue Q){
14     return Q->Front == Q->Rear;
15 }

堆疊判斷空滿只需要比較Top即可,S->m-1是陣列元素最大下標索引,-1是沒有元素入棧是的S->Top初始值。判斷佇列空滿,一般在佇列的順序儲存結構中採用迴圈佇列的方式:Rear,Front到達陣列端點時,能折回陣列開始處,即相當於陣列頭尾相接為環狀。所以,當插入或刪除操作的作用單元到達陣列末端後用公式“Rear(或Front)%陣列長度”取餘實現。

也就是說取餘操作之後回到原點,即Q->Front,證明隊滿,若沒有取餘操作時,頭尾連結串列指標相等,則證明隊空。

3.入隊及刪除操作函式

 1 bool AddQ(Queue Q ,BinTree X){//無頭結點鏈式儲存
 2     if(IsFull2(Q)){
 3         printf("佇列滿");
 4         return false; 
 5     }
 6     else{
 7         Q->Rear = (Q->Rear+1)%(Q->Max_s);
 8         Q->Date[Q->Rear] = X;
 9         return true;
10     }
11 }
12 13 BinTree DeleteQ(Queue Q){
14     if (IsEmpty2(Q)){
15         printf("佇列空");
16         return NULL;
17     }
18     else{
19         Q->Front = (Q->Front+1)%Q->Max_s;
20         return Q->Date[Q->Front];
21     }
22 }


入隊需判斷是否隊滿,刪除則相反。Q->Rear = (Q->Rear+1)%(Q->Max_s);具體意義就是Q->Rear = Q->Rear+1,取餘操作是為了避免Rear到達陣列末端,若Rear到達陣列末端就折返到陣列開始,最大化利用空間。讓尾結點指向下一個並對其賦值X,即新增成功。

刪除時從頭結點刪除,頭結點向下移一位,並返回該節點的值。

4.入棧及出棧函式

 1 bool Push (Stack S,BinTree X){
 2     if(IsFull(S)){
 3         printf("堆疊滿");
 4         return false;
 5          
 6     }
 7     else {
 8         S->Data[++(S->Top)] = X;
 9         return true;
10     }
11 }
12 13 BinTree Pop(Stack S){
14     if(IsEmpty(S)){
15         printf("堆疊空"); 
16         return NULL; 
17     }
18     else{
19         return (S->Data[(S->Top--)]); 
20      } 
21 }

同入隊一樣,入棧判棧滿,出棧判棧空。入棧時,先執行++(S->Top)棧頂指標加一,S->Data[++(S->Top)]即下一個結點賦值為樹節點反之Top--。

5.遞迴遍歷函式

 1 void PreorderTraversal(BinTree BT){
 2     if (BT){
 3         printf("%c",BT->Data);
 4         PreorderTraversal(BT->Left);
 5         PreorderTraversal(BT->Right);
 6     }
 7 } 
 8  9 void InorderTraversal(BinTree BT){
10     if(BT){
11         InorderTraversal(BT->Left);
12         printf("%c",BT->Data);
13         InorderTraversal(BT->Right);
14     }
15 } 
16 17 void PostorderTraversal(BinTree BT){
18     if(BT){
19         PostorderTraversal(BT->Left);
20         PostorderTraversal(BT->Right);
21         printf("%c",BT->Data);
22     }
23 } 
24

先序遍歷:它是指對節點的訪問是在左右子樹之前進行的。

遍歷過程:①訪問根節點 ②先序遍歷其左子樹 ③先序遍歷其右子樹

訪問時輸出,遍歷時遞迴。

中序遍歷後序遍歷同理。

6.二叉樹建立函式

 1 BinTree CreateBinTree()
 2 {
 3     ElementType ch;
 4     BinTree T;
 5     scanf("%c",&ch);
 6     if(ch == '@')
 7        T = NULL;
 8     else {
 9         T = (BinTree)malloc(sizeof(struct TNode));
10         T->Data = ch;
11         T->Left = CreateBinTree();
12         T->Right = CreateBinTree();
13     }
14 return T;
15 } 

建立樹時採用先序建立當輸入為@時,樹T = NULL,反之給T->Data賦值,生成一個結點,並建立左右子樹。

7.中序非遞迴函式

 1 void InorderTraversal2(BinTree BT){
 2     BinTree T;
 3     Stack S = CreateStack(100);
 4     T = BT;
 5     while(T||!IsEmpty(S)){
 6         while (T){
 7             Push(S,T);
 8             T = T->Left;
 9         }
10     }
11     T = Pop(S);
12     printf("%c",T->Data);
13     T = T->Right;
14 } 

中序非遞迴演算法原理是利用堆疊,入棧和出棧原理。while雙重迴圈一直向左沿途將子樹壓入棧。彈出最後一個然後向右執行一邊操作,依次迴圈。(可畫圖理解)

8.二叉樹高度計算函式

 1 int GetHeight(BinTree BT){
 2     int HL,HR,MaxH;
 3     if(BT){
 4         HL = GetHeight(BT->Left);
 5         HR = GetHeight(BT->Right);
 6         MaxH = HL>HR?HL:HR;
 7         return MaxH+1; 
 8     }
 9     else return 0;
10 } 

當查詢到底層時,即遞迴的基例返回值是0,遞迴時直接從左子樹開始,所以返回值MaxH+1(加上了根節點的那一層);

9.層序遍歷計算結點個數函式

 1 int NodeCount(BinTree BT){
 2     int Count = 0;
 3     Queue Q;
 4     BinTree T;
 5     if(!BT){
 6         return 0;
 7     }
 8     Q = CreateQueue(100);
 9     AddQ(Q,BT);
10     while(!IsEmpty2(Q)){
11         T = DeleteQ(Q);
12         Count++;
13 /*      printf("%d",T->Data);      */
14         if(T->Left) AddQ(Q,T->Left);
15         if(T->Right) AddQ(Q,T->Right);
16     } 
17     return Count;
18 } 

AddQ(Q,BT)將BT根節點加入佇列Q中,while迴圈每迴圈一次刪除佇列Q中的一個元素,並返回刪除的節點,將其左右子樹加入佇列,每刪除一個結點,計數器count加一,當佇列為空時退出迴圈,返回count。

10.計算葉結點個數函式

 1 int leafnum = 0;
 2 int  PreOrderPrintLeaves(BinTree BT){
 3     
 4     if(BT){
 5         if(!BT->Left&&!BT->Right){
 6             leafnum ++;
 7         }
 8         PreOrderPrintLeaves(BT->Left);
 9         PreOrderPrintLeaves(BT->Right);
10     }
11     return leafnum;
12 }

葉子節點是左右子樹均為NULL,所以PreOrderPrintLeaves(BT->Left);PreOrderPrintLeaves(BT->Right);兩句遞迴演算法迴圈遍歷所有節點,當左右節點都不為空時,計數器leafnum加一,最後返回。

總之,會使用遞迴演算法也是一種技巧