二叉樹是最常見最重要的數據結構之一,它的定義如下:
二叉樹(binary tree)是有限多個節點的集合,這個結合或者是空集,或者由一個根節點和兩顆互不相交的、分別稱為左子樹和右子樹的二叉樹組成。
二叉樹最基本的操作是遍歷:一般約定遍歷時左節點優先於右節點,這樣根據根節點的遍歷順序可分為三種遍歷操作:前序-先遍歷根節點,再處理左右節點;中序-先遍歷左節點,然后處理根節點,最后處理右節點;后序-先遍歷左右節點,然后處理根節點。
從上邊二叉樹定義可以看出:二叉樹使用了遞歸的概念描述。所以,二叉樹的很多操作都可以很方便的通過遞歸來實現,當中當然包裹遍歷操作。實際上,三種遍歷操作的遞歸實現,是二叉樹其他大多數操作實現的基礎:絕大多數其他操作都可以在三種遍歷中的一種的基礎上變化而來。
二叉樹通常有兩種存儲方式:順序存儲和鏈式存儲。本文代碼基於鏈式存儲方式實現,鏈式存儲節點定義如下:
typedef struct node *tree_pointer; typedef struct node { //為了簡化,節點數據用整型 int data; //指向當前節點左兒子和右兒子的指針 tree_pointer left_child, right_child; };
給出的節點定義中只有指向當前節點的左兒子和右兒子的指針,如果需要方便的知道當前的節點的父節點,可以在定義中增加指向父節點的指針: tree_pointer parent;
有了節點的定義,就可以編寫二叉樹的遍歷函數,我們先給出遞歸中序遍歷函數:
1 void inorder(tree_pointer ptr) 2 { 3 if (ptr) { 4 inorder(ptr->left_child); 5 printf("\t%d", ptr->data); 6 inorder(ptr->right_child); 7 } 8 }
函數本身非常簡潔,下面我們以圖示的二叉樹解釋函數的遍歷過程:
如上圖所示,這顆二叉樹有九個節點:ABCDEFGHI,紅線連接的“節點”表示葉子節點的“左右兒子”(實際上葉子節點沒有左右兒子,這里便於描述,假設存在這些NULL節點)
根據中序遍歷的定義,我們可以得到這顆二叉樹的中序遍歷結果為:HFIDGBEAC.
把這顆二叉樹的root節點指針作為參數傳遞給inorder函數,則函數的執行過程如下:
調用函數: inorder(root);
(下面步驟號為調用inorder函數的順序次數)
1. 第一次調用inorder,ptr指向A,函數進入if結構,執行第四行代碼,第二次調用inorder,參數為A的左兒子B;
2. ptr->B,進入if,執行line4,第三次調用inorder: inorder(B->left_child);
3. ptr->D,進入if,line4,第四次調用:inorder(D->l_c);
4. ptr->F, if, line4: inorder(F->l_c);
5. ptr->H, if, line4: inorder(H->lc);
6. ptr->NULL, 沒有進入if, return;
5. 執行line5,printf(H),執行line6: inorder(H->rc);
7. ptr->NULL, 沒有進入if, return回 5 ,5已經執行完畢,return 回4;
4. line5,printf(F),line6: inorder(F->rc);
8. ptr->I, if, line4: inorder(I->lc);
9. ptr->NULL, return-8;
8. line5, printf(I), line6: inorder(I->rc);
10. ptr->NULL, return-8, 8 完成,return-4, 4完成, return-3;
3. line5:printf(D), line6: inorder(D->rc);
11. ptr->G, line4: inorder(G->lc);
12. ptr->NULL, return-11;
11. line5: printf(G), line6: inorder(G->rc);
13. ptr->NULL, return-11, 11完, return-3, 3完,return-2;
2. line5:printf(B), line6:inorder(B->rc);
14. ptr->E, line4: inorder(E->lc);
15. ptr->NULL, return-14;
14. line5: printf(E), line6: inorder(E->rc);
16. ptr->NULL, return-14, 14 finished, return-2, 2 finished, return 1;
1. line5: printf(A), line6: inorder(A->rc);
17. ptr->C, line4: inorder(C->lc) ;
18. ptr->NULL, return-17;
17. line5: printf(C); line6: inorder(C->rc);
19. ptr->NULL, return-17, 17 finished, return 1, 1 finished ,return 回調用函數
完成。
上面的步驟手動描述了遞歸中序遍歷的執行過程,其中暗含了遞歸調用的入棧出棧過程。
前序和后序的遞歸遍歷與中序類似,代碼實現如下:
//前序遍歷 void preorder(tree_pointer ptr) { if (ptr) { printf("\t%d", ptr->data); preorder(ptr->left_child); preorder(ptr->right_child); } } //后序遍歷 void postorder(tree_pointer ptr) { if (ptr) { postorder(ptr->left_child); postorder(ptr->right_child); printf("\t%d", ptr->data); } }
函數的執行過程也與中序基本一樣,不再描述。
(在下一篇文章中將給出這些遍歷函數的完整測試代碼)