一.樹的定義
1.什麼是樹
有且只有一個根節點,有若干個互不相交的子樹。
2.專業術語
如上圖:我們解釋父節點、子節點、兄弟節點、堂兄弟節點。
父節點:A爲父節點
子節點:B、C是A的子節點
兄弟節點:B和C是兄弟節點
堂兄弟節點:D與E是堂兄弟節點
深度:樹中節點的最大層次(從根節點到最底層節點的層數,圖中深度爲3)
葉子節點:沒有子節點的節點(B、E是葉子節點)
度:子節點的個數
二.樹的分類
1.一般樹
一般樹:任意一個節點的子節點個數都不受限制。
2.二叉樹
二叉樹:任意一個節點的子節點個數最多爲2個,且子節點位置不可更改。
3.森林
森林:n個互不相交的樹的集合。
擴展:二叉樹的分類
1.一般二叉樹
2.滿二叉樹:在不增加樹層次的前提下,不可添加新節點
3.完全二叉樹:將滿二叉樹最底層最右邊的連續若干個節點
如圖,是一個滿二叉樹:
變成完全二叉樹,可以不刪除節點(完全二叉樹包含滿二叉樹),也可以刪除 G、GE、GEF、GEFD.
三.樹的存儲
1.連續存儲:
連續存儲必須是完全二叉樹的形式
爲什麼呢?
如下圖,一顆普通樹:
假設我們使用層次的關係(從左到右,從上到下的關係來存儲)
該樹在數組中的存儲結構如下:
我們根據此數組將樹還原:
到底是這種形式呢?
還是這種形式呢?
有歧義,不能確定節點的邏輯關係。
也就是,如果一棵樹不是完全二叉樹的形式,我們不能根據連續的存儲結構來還原一棵樹。
所以,連續的存儲結構必須是完全二叉樹的形式。
我們可以如下存儲:
至此,我們總結一下連續結構存儲完全二叉樹的優缺點:
優點:
1.查找某個節點的父節點與子節點很快
2.可以通過編號確定節點在第幾層
缺點:
浪費空間(如上面存儲,還得存儲空節點)
2.鏈式存儲
我們將樹的節點分爲3部分構成:
pLeft指向左子樹、pRight指向右子樹、data存放節點數據。
如下圖:
我們可以使用 先序遍歷、中序遍歷、後序遍歷 遍歷這棵樹。
鏈式存儲相對於連續存儲來說,更節省空間,所以使用範圍廣。
3.非鏈式存儲
對於下面樹的存儲:
1.雙親表示法
A:-1(代表A是根節點)
B:0(代表B的父節點是A)
C:0(代表C的父節點是A)
D:1(代表D的父節點是B)
E:1(代表E的父節點是B)
特點:求父節點很方便,但是求子節點麻煩。
2.孩子表示法
特點:求子節點很方便,但是求父節點麻煩。
3.雙親-孩子表示法
特點:求子節點很方便,求父節點也很方便,但是浪費空間。
4.二叉樹表示法
對於一般樹的存儲,我們可以使用雙親表示法、孩子表示法、雙親-孩子表示法。
我們也可以將一棵樹轉換成二叉樹來存儲。
因爲現在,我們對二叉樹的算法比較成熟,對普通樹的算法不成熟。所以我們通常將普通樹轉換成二叉樹來存儲,便於以後的操作。
我們將一顆普通樹轉變爲二叉樹來存儲的規則是:左指針域指向第1個孩子,右指針域指向兄弟。
總結:
連續存儲結構:浪費空間(必須是完全二叉樹),但是查找速度快
鏈式存儲結構:節約空間,查找速度還可以
非鏈式存儲結構:最浪費空間
我們最常用的存儲結構是:鏈式二叉樹
四.樹的操作
1.遍歷
1.1前序遍歷
前序遍歷的順序是:根節點-左子樹-右子樹
上面圖片樹的前序遍歷爲:A-B-D-E-C-F
1.2中序遍歷
中序遍歷的順序是:左子樹-根節點-右子樹
上面圖片樹的前序遍歷爲:D-B-E-A-F-C
1.3後序遍歷
後序遍歷的順序是:左子樹-右子樹-根節點
上面圖片樹的前序遍歷爲:D-E-B-F-C-A
2.根據遍歷結果還原一棵樹
我們可以根據:
先序遍歷+中序遍歷—>還原一棵樹
中序遍歷+後序遍歷—>還原一棵樹
但是不能:
前序遍歷+後序遍歷—>還原一棵樹
因爲:
前序遍歷可以確定根節點,但是不能確定左子樹、右子樹。
後序遍歷可以確定根節點,但是不能確定左子樹、右子樹。
中序遍歷可以確定左子樹、右子樹,但是不能確定根節點。
所以:
先序遍歷或後序遍歷只有和中序遍歷結合,才能確定一棵樹的根節點、左子樹、右子樹。
我們在根據 先序+中序—>還原一棵樹 或 中序+後序—>還原一棵樹 的時候,有如下規律:
先序:最先出現的是根節點
後序:最後出現的是根節點
我們根據先序和後序的規律,再結合中序,就能輕鬆的還原一棵樹。
具體的練習就不寫了,其實並不難。
五.樹的應用
1.是數據庫中數據組織的一種重要方式(如圖書管理數據庫)
2.操作系統子父進程的關係就是一棵樹。
3.面向對象中類的繼承關係
等等…
六.編碼實戰
我們創建下面圖中的這棵樹,並且實現它的前序遍歷,中序遍歷、後序遍歷。
#include <stdio.h>
#include <stdlib.h>
struct BTNode
{
char data;
struct BTNode * PTLchild;
struct BTNode * PTRchild;
};
struct BTNode *createBTree();
void preTraverseBTree();
void inTraverseBTree();
void postTraverseBTree();
int main()
{
struct BTNode * pT=createBTree();
preTraverseBTree(pT);
inTraverseBTree(pT);
postTraverseBTree(pT);
return 0;
}
//創建一棵樹
struct BTNode *createBTree()
{
struct BTNode * pA=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pB=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pC=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pD=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pE=(struct BTNode *)malloc(sizeof(struct BTNode));
pA->data='A';
pB->data='B';
pC->data='C';
pD->data='D';
pE->data='E';
pA->PTLchild=pB;
pA->PTRchild=pC;
pB->PTLchild=NULL;
pB->PTRchild=NULL;
pC->PTLchild=pD;
pC->PTRchild=NULL;
pD->PTLchild=NULL;
pD->PTRchild=pE;
pE->PTLchild=NULL;
pE->PTRchild=NULL;
return pA;
}
//先序遍歷
void preTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
printf("%c\n",pT->data);
if(pT->PTLchild!=NULL)
{
preTraverseBTree(pT->PTLchild);
}
if(pT->PTRchild!=NULL)
{
preTraverseBTree(pT->PTRchild);
}
}
}
//中序遍歷
void inTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
if(pT->PTLchild!=NULL)
{
inTraverseBTree(pT->PTLchild);
}
printf("%c\n",pT->data);
if(pT->PTRchild!=NULL)
{
inTraverseBTree(pT->PTRchild);
}
}
}
//後序遍歷
void postTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
if(pT->PTLchild!=NULL)
{
postTraverseBTree(pT->PTLchild);
}
if(pT->PTRchild!=NULL)
{
postTraverseBTree(pT->PTRchild);
}
printf("%c\n",pT->data);
}
}