資料結構 二叉樹 鏈式儲存結構
阿新 • • 發佈:2021-01-29
二叉樹 鏈式儲存結構
結構定義:
template<typename ElemType>
struct BiNode
{
ElemType data;
struct BiNode* lchild, * rchild;
};
template<typename ElemType>
using BiTree = BiNode<ElemType>*;
基本操作:
遍歷:二叉樹最基本也最重要的操作,分成了前序遍歷、中序遍歷、後序遍歷、層次遍歷四種
- 前序遍歷遞迴演算法:首先訪問根節點,然後遍歷左子樹、右子樹;左右子樹同樣先遍歷根節點,然後遍歷左右子樹,適合用遞迴解決
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;
}
- 中序遍歷遞迴演算法:首先遍歷左子樹,然後訪問根節點,然後在訪問右子樹;左右子樹同樣也是按先訪問左子樹,然後根節點,最後右子樹的順序,適合用遞迴解決
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;
}
- 中序遍歷非遞迴演算法:非遞迴演算法需要藉助棧來實現,每次都先訪問最左邊的節點,然後訪問其根節點,再訪問根節點的右子樹
佇列參考這裡
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;
}
- 後序遍歷遞迴演算法:首先訪問左右子樹,然後訪問根節點;對於左右子樹同樣也是先訪問左右子樹,再訪問根節點,適合用遞迴解決
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;
}
- 層序遍歷:按從上到下、從左到右的順序遍歷二叉樹,需要藉助佇列,每次將訪問到的節點的左右子樹依次入隊,再出隊一個元素訪問,一直迴圈直到隊空
佇列參考這裡
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;
}
建立二叉樹:
- 每次都通過手工輸入二叉樹會非常繁瑣低效,所以通常選擇事先將要建立的二叉樹寫在檔案中,或者賦給字元指標的方式建立二叉樹,但由於此時只給出一個前序序列無法唯一確定一顆二叉樹,所以還需要用特殊的符號表示出空子樹才能確定這顆二叉樹
通過檔案建立:在檔案中寫好二叉樹的前序遍歷,並且要注意空子樹需要使用特殊的字元來表示,根據前序遍歷結果和空子樹的標誌建立二叉樹
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;
}
- 二叉樹可以通過一箇中序序列和一個前序或後序序列唯一確定,所以還可以通過給出的兩個序列建立二叉樹
查詢一個元素在中序序列中的索引,因為多個地方用到並且該功能相對獨立,所以適合單獨寫一個函式
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;
}
計算:計算二叉樹的各種值,包括葉子數、節點數、深度
- 計算葉子數:通過遞迴,計算每顆子樹的左右子樹的葉子數,總葉子數即每顆子樹的葉子數之和
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);//計算自己左右子樹的葉子數
}
- 計算節點數:通過遞迴,計算左右子樹的節點數,再加上自身一個節點,即為總節點數
template<typename ElemType>
int getNodeCount(BiTree<ElemType>& T)//計算節點數
{//計算自己左右子樹的節點數再加上自己
if (!T)//為空
return 0;
else
return getNodeCount(T->lchild) + getNodeCount(T->rchild) + 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");
}
結果:
因為圖是直接從實驗報告的截圖再截圖下來的,所以可能也許大概會有那麼點點糊