1. 程式人生 > 其它 >資料結構 二叉樹 鏈式儲存結構

資料結構 二叉樹 鏈式儲存結構

技術標籤:資料結構資料結構二叉樹演算法c++指標

二叉樹 鏈式儲存結構

結構定義:

template<typename ElemType>
struct BiNode
{
	ElemType data;
	struct BiNode* lchild, * rchild;
};

template<typename ElemType>
using BiTree = BiNode<ElemType>*;

基本操作:

遍歷:二叉樹最基本也最重要的操作,分成了前序遍歷、中序遍歷、後序遍歷、層次遍歷四種

  1. 前序遍歷遞迴演算法:首先訪問根節點,然後遍歷左子樹、右子樹;左右子樹同樣先遍歷根節點,然後遍歷左右子樹,適合用遞迴解決
template<typename ElemType>
Status preOrderTraverse(BiTree<ElemType> T, Status(*visit)(BiTree<ElemType>&))//前序遍歷 遞迴演算法
{//先遍歷根節點,然後遍歷左子樹、右子樹
	if (T)
	{
		(*visit)(T);//遍歷並訪問根節點
		preOrderTraverse(T->lchild, print);//前序遍歷左子樹
		preOrderTraverse(T->rchild, print);//前序遍歷右子樹
		return OK;
}//T為空表示當前子樹遍歷結束,可以退出當前遞迴 return ERROR; }
  1. 中序遍歷遞迴演算法:首先遍歷左子樹,然後訪問根節點,然後在訪問右子樹;左右子樹同樣也是按先訪問左子樹,然後根節點,最後右子樹的順序,適合用遞迴解決
template<typename ElemType>
Status inOrderTraverse(BiTree<ElemType> T, Status(*visit)(BiTree<ElemType>&))//中序遍歷 遞迴演算法
{//先遍歷左子樹,然後遍歷根節點,最後遍歷右子樹
	if (T)
	{
		inOrderTraverse
(T->lchild, print);//中序遍歷左子樹 (*visit)(T);//遍歷並訪問根節點 inOrderTraverse(T->rchild, print);//中序遍歷右子樹 return OK; }//T為空表示當前子樹遍歷結束,可以退出當前遞迴 return ERROR; }
  1. 中序遍歷非遞迴演算法:非遞迴演算法需要藉助棧來實現,每次都先訪問最左邊的節點,然後訪問其根節點,再訪問根節點的右子樹
    佇列參考這裡
template<typename ElemType>
Status inOrderTraverse_notRescure(BiTree<ElemType> T, Status(*visit)(BiTree<ElemType>&))//中序遍歷 非遞迴演算法
{//每次都尋找最左邊的節點進行訪問,然後訪問其右子樹
	BiTree<ElemType> t;
	SqStack<BiTree<ElemType>> S;
	initSqStsck(S);
	push(S, T);
	while (!isEmpty(S))
	{
		while (getTop(S, t) && t)//尋找最左端的節點
			push(S, t->lchild);//將每個節點的左兒子入棧
		//迴圈結束,表明已經到某個節點最左端,並且此時棧頂元素為空
		pop(S, t);//彈出空指標
		if (!isEmpty(S))//棧不空
		{
			pop(S, t);//彈出棧頂元素
			(*visit)(t);//訪問
			push(S, t->rchild);//將棧頂元素右兒子入棧
		}
	}
	return OK;
}

  1. 後序遍歷遞迴演算法:首先訪問左右子樹,然後訪問根節點;對於左右子樹同樣也是先訪問左右子樹,再訪問根節點,適合用遞迴解決
template<typename ElemType>
Status postOrderTraverse(BiTree<ElemType> T, Status(*visit)(BiTree<ElemType>&))//後序遍歷 遞迴演算法
{//先訪問左子樹、右子樹,最後訪問根節點
	if (T)
	{
		postOrderTraverse(T->lchild, print);//後序遍歷左子樹
		postOrderTraverse(T->rchild, print);//後序遍歷右子樹
		(*visit)(T);
		return OK;
	}//T為空表示當前子樹遍歷結束,可以退出當前遞迴
	return ERROR;
}

  1. 層序遍歷:按從上到下、從左到右的順序遍歷二叉樹,需要藉助佇列,每次將訪問到的節點的左右子樹依次入隊,再出隊一個元素訪問,一直迴圈直到隊空
    佇列參考這裡
template<typename ElemType>
Status levelOrderTraverse(BiTree<ElemType> T, Status(*visit)(BiTree<ElemType>&))//層序遍歷
{//從左到右,從上到下依次遍歷 
	//從根節點開始,依次將該節點的左右兒子入隊,每次出隊一個元素,若非空則入隊其左右兒子,直至隊空
	SqQueue<BiTree<ElemType>> Q;
	initSqQueue(Q);
	if (T)
	{
		enSqQueue(Q, T);//根節點入隊
		while (!isSqQueueEmpty(Q))//佇列非空
		{
			deSqQueue(Q, T);//出隊一個元素
			if (T)
			{
				visit(T);
				//左右兒子節點入隊
				enSqQueue(Q, T->lchild);
				enSqQueue(Q, T->rchild);
			}
		}
		return OK;
	}
	return ERROR;
}

建立二叉樹

  1. 每次都通過手工輸入二叉樹會非常繁瑣低效,所以通常選擇事先將要建立的二叉樹寫在檔案中,或者賦給字元指標的方式建立二叉樹,但由於此時只給出一個前序序列無法唯一確定一顆二叉樹,所以還需要用特殊的符號表示出空子樹才能確定這顆二叉樹

通過檔案建立:在檔案中寫好二叉樹的前序遍歷,並且要注意空子樹需要使用特殊的字元來表示,根據前序遍歷結果和空子樹的標誌建立二叉樹

template<typename ElemType>
Status creatBiTree(BiTree<ElemType>& T)//建立二叉樹 通過檔案
{
	ElemType ch;
	char flag = '$';
	string fileName = "data01.txt";
	static ifstream ifs(fileName);
	if (!ifs.is_open())
	{
		cout << "檔案開啟失敗" << endl;
		return ERROR;
	}
	ifs >> ch;//從檔案讀取一個字元
	if (ch == flag)
		T = NULL;
	else
	{//建立新節點
		T = new BiNode<ElemType>;
		T->data = ch;
		//遞迴建立左右子樹
		creatBiTree(T->lchild);
		creatBiTree(T->rchild);
	}
	return OK;
}

通過字元指標建立:將二叉樹前序遍歷序列通過字元指標傳入進行建立

template<typename ElemType>
Status creatBiTree(BiTree<ElemType>& T, ElemType*& data)//建立二叉樹 通過傳入指標
{
	ElemType ch;
	char flag = '$';
	ch = *data;//讀取指標第一個元素
	if (ch == flag)
		T = NULL;
	else
	{//建立新節點
		T = new BiNode<ElemType>;
		T->data = ch;
		//遞迴建立左右子樹
		creatBiTree(T->lchild, ++data);//指標需要自增而不能只是簡單+1
		creatBiTree(T->rchild, ++data);
	}
	return OK;
}

  1. 二叉樹可以通過一箇中序序列和一個前序或後序序列唯一確定,所以還可以通過給出的兩個序列建立二叉樹
    查詢一個元素在中序序列中的索引,因為多個地方用到並且該功能相對獨立,所以適合單獨寫一個函式
template<typename ElemType>
int searchInLDR(ElemType et, ElemType* LDR, int length)//尋找當前元素et在LDR中的索引 未找到則返回-1
{
	int index = 0;
	while (index < length)
	{
		if (LDR[index++] == et)
			return index - 1;//因為上一句++,所以要-1
	}
	return -1;
}

通過前序序列和中序序列建立:給出前序序列可以確定根節點,給出中序序列可以確定左右子樹,二者結合就可以確定唯一一顆二叉樹

template<typename ElemType>
Status creatBiTree_DLR_LDR(BiTree<ElemType>& T, ElemType* DLR, ElemType* LDR, int length)//根據前序和中序建立二叉樹
{//前序的第一個節點即為根節點,找到該節點在中序的位置,中序序列中該節點前的元素即為該根節點的左子樹,後的即為該節點的右子樹
	if (DLR && LDR && length > 0)
	{
		T = new BiNode<ElemType>;
		T->data = DLR[0];
		T->lchild = NULL;
		T->rchild = NULL;
		int index = searchInLDR(T->data, LDR, length);//查詢根節點在中序的索引
		if (index > 0)//索引大於0,代表存在左子樹(若等於0,代表沒有左子樹)
			creatBiTree_DLR_LDR(T->lchild, DLR + 1, LDR, index);
		creatBiTree_DLR_LDR(T->rchild, DLR + 1 + index, LDR + 1 + index, length - index - 1);//右子樹靠長度判斷是否存在
		//DLR + 1 + index 和 LDR + 1 + index 用來跳過左子樹
		return OK;
	}
	return ERROR;
}

通過中序序列和後序序列建立:後序序列可以確定根節點,結合中序序列確定左右子樹,可以確定出唯一的二叉樹

template<typename ElemType>
Status creatBiTree_LDR_LRD(BiTree<ElemType>& T, ElemType* LDR, ElemType* LRD, int length)//根據中序和後序建立二叉樹
{//後序的最後一個節點即為根節點,找到該節點在中序的位置,中序序列中該節點前的元素即為該根節點的左子樹,後的即為該節點的右子樹
	//因為後序需要從最後開始找起,而左子樹一定在右子樹前面,所以要先建立右子樹
	if (LDR && LRD && length > 0)
	{
		T = new BiNode<ElemType>;
		T->data = LRD[length - 1];
		T->lchild = NULL;
		T->rchild = NULL;
		int index = searchInLDR(T->data, LDR, length);
		creatBiTree_LDR_LRD(T->rchild, LDR + index + 1, LRD + index, length - index - 1);//右子樹靠長度判斷是否存在
		//LDR + index + 1 和 LRD + index 用來跳過左子樹
		if (index > 0)//索引大於0,代表存在左子樹(若等於0,代表沒有左子樹)
			creatBiTree_LDR_LRD(T->lchild, LDR, LRD, index);
		return OK;
	}
	return ERROR;
}

計算:計算二叉樹的各種值,包括葉子數、節點數、深度

  1. 計算葉子數:通過遞迴,計算每顆子樹的左右子樹的葉子數,總葉子數即每顆子樹的葉子數之和
template<typename ElemType>
int getLeavesCount(BiTree<ElemType>& T)//計算葉子數
{//若是葉子則返回1,否則計算自己左右子樹的葉子數
	if (!T)//為空
		return 0;
	else if (T->lchild == NULL && T->rchild == NULL)//左右子樹皆為空,是葉子
		return 1;
	else//不是葉子也不為空
		return getLeavesCount(T->lchild) + getLeavesCount(T->rchild);//計算自己左右子樹的葉子數
}

  1. 計算節點數:通過遞迴,計算左右子樹的節點數,再加上自身一個節點,即為總節點數
template<typename ElemType>
int getNodeCount(BiTree<ElemType>& T)//計算節點數
{//計算自己左右子樹的節點數再加上自己
	if (!T)//為空
		return 0;
	else
		return getNodeCount(T->lchild) + getNodeCount(T->rchild) + 1;//計算自己左右子樹的節點數再加上自身算一個節點
}

  1. 計算深度:遞迴計算自己左右子樹的深度,取其中大的一個再加上自身算一層
template<typename ElemType>
int getDeepth(BiTree<ElemType>& T)//計算深度
{//計算自己左右子樹的深度加上自己,並返回左右深度中大的那個
	if (!T)//為空
		return 0;
	else
	{
		int ldeep = getDeepth(T->lchild) + 1;//計算自己左子樹的深度再加上自己
		int rdeep = getDeepth(T->rchild) + 1;//計算自己右子樹的深度再加上自己
		return ldeep > rdeep ? ldeep : rdeep;//返回左右深度中大的那個
	}
}

判斷相似:遞迴判斷左右子樹是否相似,一旦有一個子樹不相似則兩顆二叉樹不相似

template<typename ElemType>
bool isSimilar(BiTree<ElemType>T1, BiTree<ElemType>T2)//判斷相似
{//若兩棵樹均為空,則相似;若一空一非空,則不相似;若均非空,則分別判斷兩棵樹的左右子樹是否相似
	if (!T1 && !T2)//均空
		return true;
	else if ((!T1 && T2) || (!T2 && T1))//一空一非空
		return false;
	else//均非空
		return isSimilar(T1->lchild, T2->lchild) && isSimilar(T1->rchild, T2->rchild);
}

測試

//使用的檔案的內容為ABC$$DE$G$$F$$$
void testBiTree()//測試二叉樹
{
	BiTree<char> T_1;
	cout << "用檔案傳入資料建立二叉樹" << endl;
	creatBiTree(T_1);
	cout << "前序遍歷:";
	preOrderTraverse(T_1, print);
	cout << endl;
	cout << "中序遍歷:";
	inOrderTraverse(T_1, print);
	cout << endl;
	cout << "中序非遞迴遍歷:";
	inOrderTraverse_notRescure(T_1, print);
	cout << endl;
	cout << "後序遍歷:";
	postOrderTraverse(T_1, print);
	cout << endl;
	cout << "層序遍歷:";
	levelOrderTraverse(T_1, print);
	cout << endl;
	cout << "葉子數:" << getLeavesCount(T_1) << endl;
	cout << "節點數:" << getNodeCount(T_1) << endl;
	cout << "深度:" << getDeepth(T_1) << endl;

	cout << endl;
	BiTree<char> T_2;
	cout << "用字元指標傳入資料建立二叉樹" << endl;
	char* data = (char*)"-*a$$b$$c$$";//將const char* 強制轉換為char*
	creatBiTree(T_2, data);
	cout << "前序遍歷:";
	preOrderTraverse(T_2, print);
	cout << endl;
	cout << "中序遍歷:";
	inOrderTraverse(T_2, print);
	cout << endl;
	cout << "中序非遞迴遍歷:";
	inOrderTraverse_notRescure(T_2, print);
	cout << endl;
	cout << "後序遍歷:";
	postOrderTraverse(T_2, print);
	cout << endl;
	cout << "層序遍歷:";
	levelOrderTraverse(T_2, print);
	cout << endl;
	cout << "葉子數:" << getLeavesCount(T_2) << endl;
	cout << "節點數:" << getNodeCount(T_2) << endl;
	cout << "深度:" << getDeepth(T_2) << endl;

	cout << endl;
	cout << "根據前序遍歷和中序遍歷建立二叉樹" << endl;
	char* DLR_3 = (char*)"ABCDEGF";
	char* LDR_3 = (char*)"CBEGDFA";
	int length_3 = 7;
	BiTree<char> T_3;
	creatBiTree_DLR_LDR(T_3, DLR_3, LDR_3, length_3);
	cout << "前序遍歷:";
	preOrderTraverse(T_3, print);
	cout << endl;
	cout << "中序遍歷:";
	inOrderTraverse(T_3, print);
	cout << endl;
	cout << "中序非遞迴遍歷:";
	inOrderTraverse_notRescure(T_3, print);
	cout << endl;
	cout << "後序遍歷:";
	postOrderTraverse(T_3, print);
	cout << endl;
	cout << "層序遍歷:";
	levelOrderTraverse(T_3, print);
	cout << endl;
	cout << "葉子數:" << getLeavesCount(T_3) << endl;
	cout << "節點數:" << getNodeCount(T_3) << endl;
	cout << "深度:" << getDeepth(T_3) << endl;

	cout << endl;
	cout << "根據中序遍歷和後序遍歷建立二叉樹" << endl;
	char* LDR_4 = (char*)"CBEGDFA";
	char* LRD_4 = (char*)"CGEFDBA";
	int length_4 = 7;
	BiTree<char> T_4;
	creatBiTree_LDR_LRD(T_4, LDR_4, LRD_4, length_4);
	cout << "前序遍歷:";
	preOrderTraverse(T_4, print);
	cout << endl;
	cout << "中序遍歷:";
	inOrderTraverse(T_4, print);
	cout << endl;
	cout << "中序非遞迴遍歷:";
	inOrderTraverse_notRescure(T_4, print);
	cout << endl;
	cout << "後序遍歷:";
	postOrderTraverse(T_4, print);
	cout << endl;
	cout << "層序遍歷:";
	levelOrderTraverse(T_4, print);
	cout << endl;
	cout << "葉子數:" << getLeavesCount(T_4) << endl;
	cout << "節點數:" << getNodeCount(T_4) << endl;
	cout << "深度:" << getDeepth(T_4) << endl;

	cout << endl;
	cout << "T_1與T_2相似:" << isSimilar(T_1, T_2) << endl;
	cout << "T_2與T_3相似:" << isSimilar(T_2, T_3) << endl;
	cout << "T_3與T_4相似:" << isSimilar(T_3, T_4) << endl;
	cout << "T_1與T_4相似:" << isSimilar(T_1, T_4) << endl;

	cout << endl;
	system("pause");
}

結果
因為圖是直接從實驗報告的截圖再截圖下來的,所以可能也許大概會有那麼點點糊
在這裡插入圖片描述
在這裡插入圖片描述