1.樹的度
結點擁有的子樹數稱爲結點的度。度爲0的結點稱爲葉結點(leaf)或終端結點;度不爲0的結點稱爲非終端結點或分支結點。
2.分支結點
分支結點也稱爲內部結點。
3.樹的層次
結點的層次從根開始定義起,根爲第一層,根的孩子爲第二層。
4.樹的存儲結構
利用順序存儲和鏈式存儲的特色,徹底能夠實現對數的存儲結構的表示。咱們介紹三種不一樣的表示法:
雙親表示法、孩子表示法、孩子兄弟表示法
1)雙親表示法
/** 樹的雙親表示法結點結構定義 **/
#define MAX_TREE_SIZE 100
typedef int TElemType; /* 樹結點的數據類型,目前暫定爲整型 */
typedef struct PTNode /* 結點結構 */
{
TElemType data;/* 結點數據 */
int parent;/* 雙親位置 */
}PTNode;
typedef struct/* 樹結構 */
{
PTNode nodes[MAX_TREE_SIZE];/* 結點數組 */
int r,n;/* 根的位置和結點數 */
}PTree;
因爲根結點是沒有雙親的,因此咱們約定根結點的位置域設置爲-1.
存儲結構的設計是一個很是靈活的過程。一個存儲結構設計的是否合理,取決於基本該存儲結構的運算是否適合、是否方便,時間複雜度好很差等等。
2)孩子表示法
方案一:
一種是指針域的個數等於樹的度,樹的度是樹哥哥結點度的最大值。
評判:這種方法對於樹中各結點的度相差很大時,顯然是很浪費時間,由於有不少個結點,它的指針域是空的。不過若是樹的各結點度相差很小時,
那就意味着開闢的空間被充分利用了,這時存儲結構的缺點反而變成了優勢。
方案二:
每一個結點指針域的個數等於該結點的度,咱們專門取一個位置來存儲結點指針域的個數。
評判:這種方法客服了浪費空間的缺點,對空間利用率是很高了,可是因爲各個結點的鏈表是不相同的結構,加上要維護結點度的數值,在運算上
會帶來時間上的損耗。
再優化:
定義孩子表示法的結構定義代碼
/** 樹的結點表示法結構定義 **/
#define MAX_TREE_SIZE 100
typedef struct CTNode /** 孩子結點 **/
{
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct /** 表頭結構 **/
{
TElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct /** 樹結構 **/
{
CTBox nodes[MAX_TREE_SIZE];/** 結點數組 **/
int r,n;/** 根的位置和結點數 **/
}CTree;
評判:這樣的結構對於咱們要查找某個結點的某個孩子,或者找某個結點的兄弟,只須要查找這個結點的孩子單鏈表便可。對於遍歷整棵樹也很方便,可是
存在一個問題,如何知道結點的雙親是誰?在表頭結構中加一項parent的位置。
3)孩子兄弟表示法(未完待續)
任意一棵樹,它的結點的第一個孩子若是存在就是惟一的,它的右兄弟若是存在也是惟一的。
結構定義代碼:
/** 樹的孩子兄弟表示法結構定義 **/
typedef struct CSNode
{
TElemType data;
struct CSNode *firstchild,*rightsib;
}CSNode,*CSTree;
查找孩子很方便,查找雙親不是很方便,因此在有可能的條件下,能夠添加父母結點項。
5.二叉樹基本性質
性質1:在二叉樹的第i層上至多有2的(i-1)次方個結點(i>=1)
性質2:深度爲k的二叉樹至多有(2的k次)-1 個結點(意思就是這棵樹上有多少個結點,哈哈)
性質3:對任何一棵二叉樹T,若是其終端結點數爲n0,度爲2的結點數爲n2,則n0 = n2 + 1;(不理解)
終端結點數其實就是葉子結點數,而一棵二叉樹,除了葉子結點外,剩下的就是度爲1或2的結點數了,咱們設n1爲度是1的結點數。則數T結點總數爲n = n0 + n1 + n2
性質4: 具備n個結點的徹底二叉樹的深度爲log2(N) + 1
性質5:若是對一棵有n個結點的徹底二叉樹的結點按層序編號(從第一層到第log2 N + 1層,每層從左到右),對任一結點(1<=i<=n)
1)若是i = 1,則結點i是二叉樹的根,無雙親;若是,i>1,則雙親是結點的[i/2];
2)若是2i > n,則結點i無左孩子(結點i爲葉子結點);不然其左孩子是結點2i.
3)若是 2i + 1 > n,則結點i無右孩子;不然其右孩子是結點2i + 1;
6.二叉樹順序存儲結構
使用於徹底二叉樹。
7.二叉鏈表
二叉樹每一個結點最多有兩個孩子,因此它設計一個數據域和兩個指針域是比較天然的想法,咱們稱這樣的鏈表叫作二叉鏈表。
二叉鏈表的結點結構定義代碼:
/** 二叉樹的二叉鏈表的結點結構定義 **/
typedef struct BiTNode /*結點結構*/
{
TElemType data;/** 結點結構 **/
struct BitNode *lchild, *rchild;/** 左右孩子指針 **/
}BiTNode,*BitTree;
注意:若是有須要就定義一個指向父的指針。
8.遍歷二叉樹
1)前序遍歷
規則:若二叉樹爲空,則空操做返回,不然先訪問根結點,而後前序遍歷左子樹,再前序遍歷右子樹。
根-》左-》右
代碼實現:
/* 二叉樹的前序遍歷遞歸算法 */
void PreOrderTraverse(BiTree T)
{
if(T == NULL)
return ;
printf("%c",T->data);/* 顯示結點數據,能夠更改成其餘隊結點操做 */
PreOrderTraverse(T->lchild);/* 再先序遍歷左子樹 */
PreOrderTraverse(T->rchild);/* 最後先序遍歷右子樹 */
}
2)中序遍歷
規則:若樹爲空,則空操做返回,不然從根結點返回(注意,並非先訪問根結點),中序遍歷根結點的左子樹,而後是訪問根結點,最後中序遍歷右子樹。
左-》根-》右
代碼實現: /* 二叉樹的中序遍歷遞歸算法 */
void InOrderTraverse(BiTree T)
{
if(T == NULL)
return ;
InOrderTraverse(T->lchild);/*中序遍歷左子樹*/
printf("%c",T->data);/*顯示結點數據,能夠更改成其餘對結點操做*/
InOrderTraverse(T->rchild); /*最後中序遍歷右子樹*/
}
3)後序遍歷
規則:若樹爲空,則空操做返回,不然從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點。
左-》右-》根
代碼實現:
/* 二叉樹的後序遍歷遞歸算法 */
void PostOrderTraverse(BiTree T)
{
if(T == NULL)
return ;
PostOrderTraverse(T->lchild);/* 後序先遍歷左子樹 */
PostOrderTraverse(T->rchild);/* 後序再遍歷右子樹 */
printf("%c",T->data);/** 後序,顯示結點數據,能夠更改成其餘對結點的操做 **/
}
4)層序遍歷
規則:若樹爲空,則空操做返回,不然從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問。
9.二叉樹的創建
建立前序的二叉樹
代碼實現: /** 按前序輸入二叉樹中的結點的值(一個字符)**/
/** #表示空樹,構造二叉鏈表表示二叉樹T **/
void CreateBitTree(BiTree *T)
{
TElemType ch;
scanf("%c",&ch);
if(ch == '#')
{
*T = NULL;
}
else
{
*T = (BiTree)malloc(sizeof(BitNode));
if(!*T)
{
exit(OVERFLOW);
}
(*T)->data = ch;
CreateBiTree(&(*T)->lchild);/* 構建左子樹 */
CreateBiTree(&(*T)->rchild);/* 構建右子樹 */
}
}
10.線索二叉樹原理
解決問題的方向:
當二叉樹的左右孩子指針爲空的時候,這樣將浪費了內存中的空間。想找一個二叉樹的前驅和後繼,必須先對二叉樹進行遍歷,能夠考慮在建立的時候記住這些前驅和後繼,那
將是多大的時間上的節省。
定義:
指向前驅和後繼的指針稱爲線索,加上線索的二叉鏈表成爲線索鏈表,相應的二叉樹成爲線索二叉樹(Threaded Binary Tree).
線索化:
對二叉樹以某種次序遍歷使其變爲線索二叉樹的過程稱爲線索化。
判斷lchild是指向它的前驅仍是左孩子,rchild是指向它的後繼仍是右孩子:
咱們在每一個結點再增設兩個標誌域Itag和rtag,注意Itag和rtag只是存放0或1數字的布爾型變量。
結構:
lchild Itag data rtag rchild
其中: Itag爲0時指向該結點的左孩子,爲1時指向該結點的前驅。
rtag爲0時指向該結點的右孩子,爲1時指向該結點的後繼。
代碼實現:
/* 二叉樹的二叉線索存儲結構定義 */
typedef enum {Link, Thread } PointerTag;/* Link == 0 表示指向左右孩子指針 */
/* Thread == 1 表示指向前驅或後繼的線索 */
typedef struct BiThrNode /* 二叉線索存儲結點結構 */
{
TElemType data; /* 結點數據 */
struct BiThrNode *lchild, *rchild;/* 左右孩子指針 */
PointerTag LTag;
PointerTag RTag; /* 左右標誌 */
}BiThrNode,*BiThrTree;
實質:
將二叉鏈表中的空指針改成指向前驅或後繼的線索。因爲前驅和後繼的信息只有在遍歷該二叉樹時才能獲得,因此線索化的過程就是遍歷的過程當中修改空指針的過程。
中序遍歷線索化的遞歸代碼實現:
BiThrTree pre; /* 全局變量,始終指向剛剛訪問過的結點 */
/* 中序遍歷進行中序線索化 */
void InThreaing(BiThrTree p)
{
if(p)
{
InThreading(p->lchild); /* 遞歸左子樹線索化 */
if( !p->lchild) /* 沒有左孩子 */
{
p->LTag = Thread; /* 前驅線索 */
p->lchild = pre; /* 左孩子指針指向前驅*/
}
if(!pre->rchild) /* 前繼沒有右孩子 */
{
pre->RTag = Thread; /* 後繼線索 */
pre->rchild = p; /* 前驅右孩子指針指向後繼 */
}
pre = p;/* 保持pre指向p的前驅 */
InThreading(p->rchild);/* 遞歸右子樹線索化 */
}
}
有了線索二叉樹後,咱們對它進行遍歷時發現,其實就等因而操做一個雙向鏈表結構。
遍歷的代碼以下:
/* T指向頭結點,頭結點左鏈lchild指向根結點,頭結點右鍵rchild指向中序遍歷的*/
/* 最後一個結點,中序遍歷二叉線索鏈表表示的二叉樹T */
static InOrderTraverse_Thr(BiThrTree T)
{
BiThrTree p;
p = T->lchild; /* p指向根結點 */
while(p != T) /* 空樹或遍歷結束時, p == T*/
{
while(p->LTag == Link)/* 當LTag == 0 時循環到中序序列第一個結點 */
p = p->lchild;
printf("%c",p->data); /*顯示結點數據,能夠更改成其餘隊結點操做 */
while(p->RTag == Thread && p->rchild != T)
{
p = p->rchild;
printf("%c",p->data);
}
p = p->rchild;/* p 進至右子樹根 */
}
reutrn OK;
}
注意:和雙向鏈表結構同樣,在二叉樹線索鏈表上添加一個頭結點,並令其lchild域的指針指向二叉樹的根結點,其rchild域的
指針指向中序遍歷時訪問的最後一個結點。反而,令二叉樹的中序序列中的一個結點中,lchild域指針和最後一個結點的rchild
域指針均指向頭結點。這樣的好處就是咱們便可以從第一個結點起順後繼進行遍歷,也能夠從最後一個結點起順前驅進行遍歷。
小結:
因爲它充分利用了空指針域的空間(這等於節省了空間),又保證了建立時的一次遍歷就能夠終生受用前驅後繼的信息。因此在實際
問題中,若是所用的二叉樹需常常遍歷或查找結點時須要某種遍歷序列中的前驅和後繼,那麼採用線索二叉表的存儲結構就是很是不錯的選擇。
11. 樹、森林與二叉樹的轉換
1)樹轉換爲二叉樹
1.加線,在全部兄弟之間加一條連線
2.去線,對樹中每一個結點,只保留它與第一個孩子結點的連線,刪除它與其餘孩子結點之間的連線
3.層次調整,以樹的根結點爲軸心,將整棵樹順時針旋轉必定的角度,使之結構井井有條。注意第一個孩子是二叉樹結點的左孩子,兄弟轉換
過來的孩子是結點的右孩子。
2)森林轉換爲二叉樹
森林是由若干棵樹組成的。
1.把每一個樹轉換爲二叉樹
2.第一棵二叉樹不動,從第二棵二叉樹開始,一次把後一棵二叉樹的根結點做爲前一棵二叉樹的根節點的右孩子,用線鏈接起來。當全部的二叉樹鏈接
後就獲得右森林轉換來的二叉樹。
3)二叉樹轉換爲樹
1.加線,若某結點的左孩子結點存在,則將這個左孩子的右孩子結點、右孩子的右孩子結點、右孩子的右孩子的右孩子結點。。。哈,反正就是左孩子
的n個右孩子結點都做爲此結點的孩子。將該結點與這些右孩子結點用線鏈接起來。
2.去線。刪除原二叉樹中全部結點與其右孩子結點的連線
3.層次調整,使之結構井井有條。
4)二叉樹轉換爲森林
判斷一棵二叉樹可以轉換成一棵樹仍是森林,標準很簡單,那就是隻要看這棵二叉樹的根結點有沒有右孩子,有就是森林,沒有就是一棵樹,
轉換森林的方法:
1.從根結點開始,若右孩子存在,則把與右孩子結點的連線刪除,再查看分離後的二叉樹,若右孩子存在,則連線刪除。。。,直到右孩子連線都刪除
爲止,獲得分離的二叉樹。
2.再將每棵分離後的二叉樹轉換爲樹便可
5)樹與森林的遍歷
樹的遍歷分爲兩種方式
1.一種是先根遍歷樹,即先訪問樹的根結點,而後依次先根遍歷根的每棵子樹。
2。另外一種是後根遍歷,即先依次後根遍歷每棵樹,而後再訪問根節點。
森林的遍歷也爲兩種方式
1.前序遍歷:先訪問深林中第一棵樹的根結點,而後再依次先根遍歷根的每棵子樹,再依次用一樣方式遍歷除去第一棵樹的剩餘樹構成的森林。
2.後序遍歷:是先訪問森林中第一棵樹,後根遍歷方式遍歷每一棵樹,而後再訪問根結點,再依次一樣方式遍歷除去第一棵樹的剩餘樹構成的森林。
12.赫夫曼樹
1)路徑長度:
從樹中一個結點到另外一個結點之間的分支構成兩個結點之間的路徑,路徑上的分支數目稱做路徑長度。
樹的路徑長度就是從樹根到每一結點的路徑長度之和。
2)帶權路徑:
結點帶權的路徑長度爲從該結點到樹根之間的路徑長度與結點上權的乘積。
樹的帶權路徑長度爲樹中全部葉子結點的帶權路徑長度之和。
*帶權路徑長度WPL最小的二叉樹稱作赫夫曼樹
3)構造最優二叉樹
沒啥好說的,一看就懂。node