1. 程式人生 > >二叉樹的遍歷及常用演算法

二叉樹的遍歷及常用演算法

# 二叉樹的遍歷及常用演算法 ## 遍歷的定義: ​ 按照某種次序訪問二叉樹上的所有結點,且每個節點僅被訪問一次; 遍歷的重要性: ​ 當我們需要對一顆二叉樹進行,插入,刪除,查詢等操作時,通常都需要先遍歷二叉樹,所有說:遍歷是二叉樹的基本操作; ## 遍歷思路: - 二叉樹的資料結構是遞迴定義(每個節點都可能包含相同結構的子節點),所以遍歷也可以使用遞迴,即結點不為空則繼續遞迴呼叫 - 每個節點都有三個域,資料與,左孩子指標和右孩子之指標,每次遍歷只需要讀取資料,遞迴左子樹,遞迴右子樹,這三個操作 ## 三種遍歷次序: 根據訪問三個域的不同順序,可以有多種不同的遍歷次序,而通常對於子樹的訪問都按照從左往右的順序; 設:L為遍歷左子樹,D為訪問根結點,R為遍歷右子樹,且L必須位於R的前面 可以得出以下三種不同的遍歷次序: ### 先序遍歷 操作次序為DLR,首先訪問根結點,其次遍歷 根的左子樹,最後遍歷根右子樹,對每棵子樹同樣按 這三步(先根、後左、再右)進行 ### 中序遍歷 操作次序為LDR,首先遍歷根的左子樹,其次 訪問根結點,最後遍歷根右子樹,對每棵子樹同樣按 這三步(先左、後根、再右)進行 ### 後序遍歷 操作次序為LRD,首先遍歷根的左子樹,其次 遍歷根的右子樹,最後訪問根結點,對每棵子樹同樣 按這三步(先左、後右、最後根)進行 ### 層次遍歷 層次遍歷即按照從上到下從左到右的順序依次遍歷所有節點,實現層次遍歷通常需要藉助一個佇列,將接下來要遍歷的結點依次加入佇列中; ## 遍歷的應用 “遍歷”是二叉樹各種操作的基礎,可以在遍歷 過程中對結點進行各種操作,如:對於一棵已知二叉樹 - 求二叉樹中結點的個數 - 求二叉樹中葉子結點的個數; - 求二叉樹中度為**1**的結點個數 - 求二叉樹中度為2的結點個數 - 5求二叉樹中非終端結點個數 - 交換結點左右孩子 - 判定結點所在層次 等等... # C語言實現: ```text #include //二叉連結串列資料結構定義 typedef struct TNode { char data; struct TNode *lchild; struct TNode *rchild; } *BinTree, BinNode; //初始化 //傳入一個指標 令指標指向NULL void initiate(BinTree *tree) { *tree = NULL; } //建立樹 void create(BinTree *BT) { printf("輸入當前結點值: (0則建立空節點)\n"); char data; scanf(" %c", &data);//連續輸入整形和字元時.字元變數會接受到換行,所以加空格 if (data == 48) { *BT = NULL; return; } else { //建立根結點 //注意開闢的空間大小是結構體的大小 而不是結構體指標大小,寫錯了不會立馬產生問題,但是後續在其中儲存資料時極有可能出現記憶體訪問異常(飆淚....) *BT = malloc(sizeof(struct TNode)); //資料域賦值 (*BT)->data = data; printf("輸入節點 %c 的左孩子 \n", data); create(&((*BT)->lchild));//遞迴建立左子樹 printf("輸入節點 %c 的右孩子 \n", data); create(&((*BT)->rchild));//遞迴建立右子樹 } } //求雙親結點(父結點) BinNode *Parent(BinTree tree, char x) { if (tree == NULL) return NULL; else if ((tree->lchild != NULL && tree->lchild->data == x) || (tree->rchild != NULL && tree->rchild->data == x)) return tree; else{ BinNode *node1 = Parent(tree->lchild, x); BinNode *node2 = Parent(tree->rchild, x); return node1 != NULL ? node1 : node2; } } //先序遍歷 void PreOrder(BinTree tree) { if (tree) { //輸出資料 printf("%c ", tree->data); //不為空則按順序繼續遞迴判斷該節點的兩個子節點 PreOrder(tree->lchild); PreOrder(tree->rchild); } } //中序 void InOrder(BinTree tree) { if (tree) { InOrder(tree->lchild); printf("%c ", tree->data); InOrder(tree->rchild); } } //後序 void PostOrder(BinTree tree) { if (tree) { PostOrder(tree->lchild); PostOrder(tree->rchild); printf("%c ", tree->data); } } //銷燬結點 遞迴free所有節點 void DestroyTree(BinTree *tree) { if (*tree != NULL) { printf("free %c \n", (*tree)->data); if ((*tree)->lchild) { DestroyTree(&((*tree)->lchild)); } if ((*tree)->rchild) { DestroyTree(&((*tree)->rchild)); } free(*tree); *tree = NULL; } } // 查詢元素為X的結點 使用的是層次遍歷 BinNode *FindNode(BinTree tree, char x) { if (tree == NULL) { return NULL; } //佇列 BinNode *nodes[1000] = {}; //佇列頭尾位置 int front = 0, real = 0; //將根節點插入到佇列尾 nodes[real] = tree; real += 1; //若佇列不為空則繼續 while (front != real) { //取出佇列頭結點輸出資料 BinNode *current = nodes[front]; if (current->data == x) { return current; } front++; //若當前節點還有子(左/右)節點則將結點加入佇列 if (current->lchild != NULL) { nodes[real] = current->lchild; real++; } if (current->rchild != NULL) { nodes[real] = current->rchild; real++; } } return NULL; } //層次遍歷 // 查詢元素為X的結點 使用的是層次遍歷 void LevelOrder(BinTree tree) { if (tree == NULL) { return; } //佇列 BinNode *nodes[1000] = {}; //佇列頭尾位置 int front = 0, real = 0; //將根節點插入到佇列尾 nodes[real] = tree; real += 1; //若佇列不為空則繼續 while (front != real) { //取出佇列頭結點輸出資料 BinNode *current = nodes[front]; printf("%2c", current->data); front++; //若當前節點還有子(左/右)節點則將結點加入佇列 if (current->lchild != NULL) { nodes[real] = current->lchild; real++; } if (current->rchild != NULL) { nodes[real] = current->rchild; real++; } } } //查詢x的左孩子 BinNode *Lchild(BinTree tree, char x) { BinTree node = FindNode(tree, x); if (node != NULL) { return node->lchild; } return NULL; } //查詢x的右孩子 BinNode *Rchild(BinTree tree, char x) { BinTree node = FindNode(tree, x); if (node != NULL) { return node->rchild; } return NULL; } //求葉子結點數量 int leafCount(BinTree *tree) { if (*tree == NULL) return 0; //若左右子樹都為空則該節點為葉子,且後續不用接續遞迴了 else if (!(*tree)->lchild && !(*tree)->rchild) return 1; else //若當前結點存在子樹,則遞迴左右子樹, 結果相加 return leafCount(&((*tree)->lchild)) + leafCount(&((*tree)->rchild)); } //求非葉子結點數量 int NotLeafCount(BinTree *tree) { if (*tree == NULL) return 0; //若該結點左右子樹均為空,則是葉子,且不用繼續遞迴 else if (!(*tree)->lchild && !(*tree)->rchild) return 0; else //若當前結點存在左右子樹,則是非葉子結點(數量+1),在遞迴獲取左右子樹中的非葉子結點,結果相加 return NotLeafCount(&((*tree)->lchild)) + NotLeafCount(&((*tree)->rchild)) + 1; } //求樹的高度(深度) int DepthCount(BinTree *tree) { if (*tree == NULL) return 0; else{ //當前節點不為空則深度+1 在加上子樹的高度, int lc = DepthCount(&((*tree)->lchild)) + 1; int rc = DepthCount(&((*tree)->rchild)) + 1; return lc > rc?lc:rc;// 取兩子樹深度的 最大值 } } //刪除左子樹 void RemoveLeft(BinNode *node){ if (!node) return; if (node->lchild) DestroyTree(&(node->lchild)); node->lchild = NULL; } //刪除右子樹 void RemoveRight(BinNode *node){ if (!node) return; if (node->rchild) DestroyTree(&(node->rchild)); node->rchild = NULL; } int main() { BinTree tree; create(&tree); BinNode *node = Parent(tree, 'G'); printf("G的父結點為%c\n",node->data); BinNode *node2 = Lchild(tree, 'D'); printf("D的左孩子結點為%c\n",node2->data); BinNode *node3 = Rchild(tree, 'D'); printf("D的右孩子結點為%c\n",node3->data); printf("先序遍歷為:"); PreOrder(tree); printf("\n"); printf("中序遍歷為:"); InOrder(tree); printf("\n"); printf("後序遍歷為:"); PostOrder(tree); printf("\n"); printf("層次遍歷為:"); LevelOrder(tree); printf("\n"); int a = leafCount(&tree); printf("葉子結點數為%d\n",a); int b = NotLeafCount(&tree); printf("非葉子結點數為%d\n",b); int c = DepthCount(&tree); printf("深度為%d\n",c); //查詢F節點 BinNode *node4 = FindNode(tree,'C'); RemoveLeft(node4); printf("刪除C的左孩子後遍歷:"); LevelOrder(tree); printf("\n"); RemoveRight(node4); printf("刪除C的右孩子後遍歷:"); LevelOrder(tree); printf("\n"); //銷燬樹 printf("銷燬樹 \n"); DestroyTree(&tree); printf("銷燬後後遍歷:"); LevelOrder(tree); printf("\n"); printf("Hello, World!\n"); return 0; } ``` ##### 測試: 測試資料為下列二叉樹: ![](https://img2020.cnblogs.com/blog/1440878/202005/1440878-20200522192146177-632875422.png) 執行程式複製貼上下列內容: ```text A B D G 0 0 H 0 0 E 0 0 C K 0 0 F I 0 J 0 0 0 ``` 特別感謝