1. 程式人生 > >二叉樹的遍歷、高度、葉子數量、拷貝以及釋放

二叉樹的遍歷、高度、葉子數量、拷貝以及釋放

先給出二叉樹的結構體定義:

#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);
}