二叉樹的遍歷、高度、葉子數量、拷貝以及釋放
先給出二叉樹的結構體定義:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
struct BinaryTree
{
char value;
struct BinaryTree *LChild;
struct BinaryTree *RChild;
};
再提供一個main方法以便二叉樹的建立與函式的呼叫:
int main(int argc, char *argv[]) { struct BinaryTree g = { '8', NULL, NULL }; struct BinaryTree f = { '6', NULL, NULL }; struct BinaryTree e = { '4', NULL, NULL }; struct BinaryTree d = { '2', NULL, NULL }; struct BinaryTree c = { '7', &f, &g }; struct BinaryTree b = { '3', &d, &e }; struct BinaryTree a = { '5', &b, &c }; recursion(&a); printf("\n"); int height = getTreeHeight_another(&a); printf("樹的高度為:%d\n", height); int num = getLeafNum(&a); printf("葉子的數量:%d\n", num); struct BinaryTree *newTree = TreeCopy(&a); printf("遍歷拷貝後的二叉樹:"); recursion(newTree); printf("\n"); destroy(&newTree); system("pause"); return 0; }
一.樹的高度:
樹的高度從下往上增長,最底層為1。
求樹的高度本身也是一個遞迴的問題:樹的高度等於最高的子樹的高度加上1。
所以也要深入的樹的最底層,直到沒有左右子樹的節點,均返回0,之後返回1,
回溯到上層函式。此時上層遞迴的左節點深入的語句已經執行完畢,所以開始右
節點深入,此處的右節點與左節點情況相同,高度同樣為1,從上至下的第2層遞
歸所以整體返回2。最終整個函式返回3,遞迴深入最深為4層。
int getTreeHeight(struct BinaryTree *root) { if (root == NULL)return 0; int LHeight = getTreeHeight(root->LChild); int RHeight = getTreeHeight(root->RChild); return LHeight > RHeight ? LHeight + 1 : RHeight + 1; }
可以更改遞迴的推出條件,以減少遞迴的深入層次,這樣遞給的深入層次最多三層。
int getTreeHeight_another(struct BinaryTree *root) { if (root->LChild == NULL && root->RChild == NULL)return 1; int LHeight = getTreeHeight(root->LChild); int RHeight = getTreeHeight(root->RChild); return LHeight > RHeight ? LHeight + 1 : RHeight + 1; }
二.遞迴遍歷
//遞迴遍歷
void recursion(struct BinaryTree *root)
{
if (!root)return;
putchar(root->value);
putchar(' ');
recursion(root->LChild);
recursion(root->RChild);
}
三.統計葉子數量
判斷葉子方法就是:判斷一個節點沒有左子樹和右子樹,那麼這個節點就是一個葉子節點。
那麼統計葉子數量可以通過返回值,將左子樹與右子樹返回的的葉子數量分別相加,這種定義方式
本身就是遞迴。
統計葉子數量同樣需要深入到樹的最底層,所以如以下程式碼所示:先從左節點開始滲入,直到找到一個
左右節點均為NULL的葉子節點,此時會返回一個1;之後回溯到上層函式,繼續執行“加號”後面的得到
右子樹葉子數量的程式碼,此處同樣也返回一個1,相加結果為2,總體回溯到它的呼叫函式即第一層也就是
使用者直接呼叫的函式,執行後面對根節點右子樹葉子數量高的計算。
int getLeafNum(struct BinaryTree *root)
{
if (root->LChild == NULL && root->RChild == NULL)return 1;
return getLeafNum(root->LChild) + getLeafNum(root->RChild);
}
四.二叉樹的拷貝
樹的拷貝直到找到一個節點它的左子樹返回NULL,右子樹也返回NULL,
那麼就為它自身開闢空間,併為其成員變數賦值:掛上左子樹、右子樹,並儲存value。
之後再返回這個節點作為上次遞迴的左或右孩子,直到root就是呼叫函式時傳入的root時,
給其成員賦值,然後結束遞迴。
此遞迴案例需要注意的是:拷貝動作需要從葉子開始,所以就要先深入到樹的最底層,
然後建立節點,進行拷貝。
struct BinaryTree *TreeCopy(struct BinaryTree *root)
{
if (!root)return NULL;
struct BinaryTree *LChild = TreeCopy(root->LChild);
struct BinaryTree *RChild = TreeCopy(root->RChild);
struct BinaryTree *myRoot = malloc(sizeof(struct BinaryTree));
myRoot->LChild = LChild;
myRoot->RChild = RChild;
myRoot->value = root->value;
return myRoot;
}
五.二叉樹的釋放
此函式用於釋放TreeCopy返回的二叉樹,使用它去釋放使用者建立的二叉樹是非法的操作。
要首先深入到葉子結點,之後逐層向上釋放,否則先釋放根節點,而後面的子樹就無法進行操作,
會造成記憶體洩漏。
此處選擇的是使用傳入二級地址,因為我想要將釋放後的節點賦值為NULL(當然這個被遞迴釋
放掉的空間以後也不會使用,完全不必賦值為NULL),所以需要使用*root的地址來完成。需要
注意的點是在深入遞迴傳遞引數時,要對子樹進行取地址操作,對應引數所要求的二級指標。
這裡還是先選擇釋放左子樹,當找到左右均為NULL的葉子節點時,將其釋放並賦值為NULL,之後
回溯到上層遞迴中,執行未執行的釋放右子樹的程式碼,此時為遞迴的第二層。
void destroy(struct BinaryTree **root)
{
if ((*root)->LChild == NULL && (*root)->RChild == NULL)
{
putchar((*root)->value), putchar(' '), free(*root), *root = NULL;
return;
}
destroy(&(*root)->LChild);
destroy(&(*root)->RChild);
}