樹和二叉樹的知識總結
思維導圖
樹和二叉樹的基本概念和算法
思維導圖
樹和二叉樹的基本概念和算法****
1樹的基本概念
樹的定義:樹(Tree)是n(n≧0)個結點的有限集。n=0時稱為空樹。在任意一顆非空樹中:有且僅有一個特定的稱為根的結點。當
n>1時,其余結點可分為m(m>0)個互不相交的有限集T1、T2、T3……、Tm,其中每個集合本身又是一棵樹,並且稱為根的子樹。
樹的基本術語:
( b ) ------------------------ ( c )
1.結點: 樹中的一個獨立單元,包含一個數據元素及若干指向其子樹的分支;如:圖C中的A、B、C。
2.結點的度:結點擁有的子樹數; 如:圖c中A的度為3。
3.樹的度:樹內各節點度的最大值;如圖C的度為3;樹中所有結點最大度數為m的有序樹稱為 m叉樹。
4.葉子(終端節點):度為0的結點;如:E、F等。
5.非終端結點(分支節點):度不為0的結點;除根節點外,非終端結點也稱為內部結點。
6.雙親和孩子:結點的子樹的根稱為該節點的孩子,該節點稱為孩子的雙親:如:B的雙親為A;B的孩子有E和F。
7.兄弟:同一個雙親的孩子之間互稱兄弟;如E、F互為兄弟。
8.祖先:從根到該節點所經分支上的所有結點;如:F的祖先是A、B。
9.子孫:以某結點為根的子樹中的任一結點;如:A的子孫有:E、B、H等。
10.層次:結點的層次從根開始定義起,樹中任一結點的層次都等於雙親結點的層次加1。
11.堂兄弟:雙親在同一層的結點互為堂兄弟,如G和H互為堂兄弟。
12.樹的深度:樹中結點的最大層次稱為樹的深度或者高度。
13.有序樹和無序樹:如果將樹中結點的各子樹看成從左到右是有次序的,則為有序樹;樹的最左邊為第一個孩子,最右邊為最后一個孩子。
14.森林:是x個互不相交的樹的集合。
樹的性質:
性質一:樹中結點數等於所有節點的度數和加一。
性質二:度為m的樹中第i層最多有m的i-1次方個節點(i>=1)。
性質三:高度h的m次樹最多有m的h次方再-1后除以m-1。
性質四:具有n個節點的m次樹的最小高度為logm(n(m-1)+1)。
樹的基本運算:
1.先根遍歷:先訪問根節點,后按照從左到右的順序先根遍歷根節點的每一棵子樹。
2.后跟遍歷:先按照從左到右的順序遍歷根節點的每一棵子樹后遍歷根節點。
3.層次遍歷:從根節點開始從上往下從左到右的次序訪問樹中的每一個節點。
樹的存儲結構:雙親存儲結構,孩子鏈存儲結構,孩子兄弟鏈存儲結構。
2二叉樹的概念和性質
二叉樹的定義:1.定義:把滿足以下兩個條件的樹型結構叫做二叉樹
1) 每個結點的度都不大於 2;(2) 每個結點的孩子結點次序不能任意顛倒。
由此定義可看出,一個二叉樹中的每個結點只能含有 0、1 或 2 個孩子,而且每個孩子有左右之分。位於左邊的孩子叫做左孩子,位於右邊的孩子叫做右孩子。
二叉樹的性質:
性質一:非空二叉樹上的葉子節點數等於雙分支結點數加一。
性質二:非空二叉樹的第i層上最多有2的i減一次方個節點。
性質三:高度為h的二叉樹最多有2deh次方再減一。
二叉樹與樹森林的轉換(詳細例子於課本201頁):
1.樹轉換為二叉樹
樹中每個節點最多只有一個最左邊的孩子(長子)和一個右鄰的兄弟
1)在所有兄弟節點之間加一連線
2)每個節點,除了保留與其長子之間的連線外,去掉該節點與其它孩子的連線
3)層次調整。以樹根點為軸心,將整棵樹順時針旋轉一定的角度,使之結構層次分明。這樣結點的左孩子還是之前的左孩子,右孩子是之前的兄弟結點
2.森林轉化為二叉樹
1)把每棵樹轉換為二叉樹。
2)第一棵二叉樹不動,從第二棵二叉樹開始,依次把后一棵二叉樹的根結點作為前一棵二叉樹的根結點的右孩子,用線連接起來。
3.二叉樹轉換為樹
是樹轉換為二叉樹的逆過程。
1)加線。若某結點X的左孩子結點存在,則將這個左孩子的右孩子結點、右孩子的右孩子結點、右孩子的右孩子的右孩子結點…,都作為結點X的孩子。將結點X與這些右孩子結點用線連接起來。
2)去線。刪除原二叉樹中所有結點與其右孩子結點的連線。
3)層次調整。
4.二叉樹轉換為森林
假如一棵二叉樹的根節點有右孩子,則這棵二叉樹能夠轉換為森林,否則將轉換為一棵樹。
1)從根節點開始,若右孩子存在,則把與右孩子結點的連線刪除。再查看分離后的二叉樹,若其根節點的右孩子存在,則連線刪除…。直到所有這些根節點與右孩子的連線都刪除為止。
2)將每棵分離后的二叉樹轉換為樹。
3二叉樹的存儲結構
順序存儲結構:二叉樹的順序存儲結構是指用一組地址連續的存儲單元依次自上而下、自左至右存儲完全二叉樹上的結點元素,即將完全二叉樹上編號為i的結點元素存儲在某個數組下標為i-1的分量中,然后通過一些方法確定結點在邏輯上的父子和兄弟關系。完全二叉樹和滿二叉樹采用順序存儲比較合適,節省空間且數組元素下標值能確定結點在二叉樹中的位置和結點之間的關系。但對於一般二叉樹,則需要添加一些並不存在的空結點,所以效率並不高。
順序存儲示意圖如下:
鏈式存儲結構: 鏈式結構是指用一個鏈表來存儲一棵二叉樹,二叉樹中的每個結點用鏈表的一個鏈結點來存儲。在二叉樹中,結點結構通常包括若干數據域和若干指針域。二叉鏈表至少包含3個域:數據域data、左指針域lchild和右指針域rchild,若下圖所示:
常用的二叉鏈表存儲結構如下圖所示:
代碼如下:
typedef struct BiTNode{
ElemType data; //數據域
struct BiTNode *lchild, *rchild; //左、右孩子指針
}BiTNode, *BiTree;
4二叉樹的基本運算及實現(詳見課本207頁)
1.創建二叉樹
2.銷毀二叉樹
3.查找節點
4.找孩子節點
5.求高度
6.輸出二叉樹
5二叉樹的遍歷
先序遍歷:先訪問根節點,后先序遍歷左子樹,在先序遍歷右子樹(個人理解:根>左>右)。
void PreOrder(BiTree t)
{
if(t){
cout<<t->data;
PreOrder(t->lchild);
PreOrder(t->rchild);
}
}
中序遍歷:中序遍歷左子樹,后訪問根節點,最后中序遍歷右子樹(個人理解:先最左節點)。
{
if(t){
InOrder(t->lchild);
cout<<t->data;
InOrder(t->rchild);
}
}
后序遍歷:后續遍歷左子樹,再后序遍歷右子樹,最后訪問根節點(個人理解:左>右>根)。
void PostOrder(BiTree t)
{
if(t){
PostOrder(t->lchild);
PostOrder(t->rchild);
cout<<t->data;
}
}
層次遍歷:從上自下從左到右訪問節點。
遍歷的遞歸算法詳見課本212頁,非遞歸算法課本218頁。
6二叉樹的構造
這里的構造二叉樹是根據二叉樹的中序序列和后序序列來構造一棵二叉樹,我們容易發現,在后序序列當中從后往前看就是樹的一個個“根”節點,因此我們得到了根節點就可以將中序序列分成兩個部分(這也符合二叉樹的定義),這樣后序序列也相應的被分為除了最后一個根節點的兩個部分,即根的左子樹和右子樹(與中序序列對應),然后再從后序序列的左子樹中找到根,右子樹中找到根,遞歸構造即可。(代碼詳見230頁)
7線索二叉樹
線索二叉樹的概念:
n個結點的二叉鏈表中含有n+1個空指針域。利用二叉鏈表中的空指針域,存放指向結點在某種遍歷次序下的前趨和后繼結點的指針(這種附加的指針稱為"線索")。這種加上了線索的二叉鏈表稱為線索鏈表,相應的二叉樹稱為線索二叉樹(Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分為前序線索二叉樹、中序線索二叉樹和后序線索二叉樹三種。
線索化二叉樹:
8哈夫曼樹
哈夫曼樹(Huffman Tree),又稱最優二叉樹,是一類帶權路徑長度最短的樹。假設有n個權值{w1,w2,...,wn},如果構造一棵有n個葉子節點的二叉樹,而這n個葉子節點的權值是{w1,w2,...,wn},則所構造出的帶權路徑長度最小的二叉樹就被稱為赫夫曼樹。
這里補充下樹的帶權路徑長度的概念。樹的帶權路徑長度指樹中所有葉子節點到根節點的路徑長度與該葉子節點權值的乘積之和,如果在一棵二叉樹中共有n個葉子節點,用Wi表示第i個葉子節點的權值,Li表示第i個也葉子節點到根節點的路徑長度,則該二叉樹的帶權路徑長度 WPL=W1L1 + W2L2 + ... Wn*Ln。
根據節點的個數以及權值的不同,哈夫曼樹的形狀也各不相同,哈夫曼樹具有如下特性:
1.對於同一組權值,所能得到的哈夫曼樹不一定是唯一的。
2.哈夫曼樹的左右子樹可以互換,因為這並不影響樹的帶權路徑長度。
3.帶權值的節點都是葉子節點,不帶權值的節點都是某棵子二叉樹的根節點。
4.權值越大的節點越靠近赫夫曼樹的根節點,權值越小的節點越遠離赫夫曼樹的根節點。
5.哈夫曼樹中只有葉子節點和度為2的節點,沒有度為1的節點。
6.一棵有n個葉子節點的赫夫曼樹共有2n-1個節點。
哈夫曼樹的構建步驟如下:
1、將給定的n個權值看做n棵只有根節點(無左右孩子)的二叉樹,組成一個集合HT,每棵樹的權值為該節點的權值。
2、從集合HT中選出2棵權值最小的二叉樹,組成一棵新的二叉樹,其權值為這2棵二叉樹的權值之和。
3、將步驟2中選出的2棵二叉樹從集合HT中刪去,同時將步驟2中新得到的二叉樹加入到集合HT中。
4、重復步驟2和步驟3,直到集合HT中只含一棵樹,這棵樹便是赫夫曼樹。
假如給定如下5個權值:
則按照以上步驟,可以構造出如下面左圖所示的哈夫曼樹,當然也可能構造出如下面右圖所示的哈夫曼樹,這並不是唯一的。
Huffman編碼
哈夫曼樹的應用十分廣泛,比如眾所周知的在通信電文中的應用。在等傳送電文時,我們希望電文的總長盡可能短,因此可以對每個字符設計長度不等的編碼,讓電文中出現較多的字符采用盡可能短的編碼。為了保證在譯碼時不出現歧義,我們可以采取如下圖所示的編碼方式:
即左分支編碼為字符0,右分支編碼為字符1,將從根節點到葉子節點的路徑上分支字符組成的字符串作為葉子節點字符的編碼,這便是赫夫曼編碼。我們根據上面左圖可以得到各葉子節點的赫夫曼編碼如下:
權值為5的節點的哈夫曼編碼為:11
權值為4的節點的哈夫曼編碼為:10
權值為3的節點的哈夫曼編碼為:00
權值為2的節點的哈夫曼編碼為:011
權值為1的節點的哈夫曼編碼為:010
而對於上面右圖,則可以得到各葉子節點的哈夫曼編碼如下:
權值為5的節點的哈夫曼編碼為:00
權值為4的節點的哈夫曼編碼為:01
權值為3的節點的哈夫曼編碼為:10
權值為2的節點的哈夫曼編碼為:110
權值為1的節點的哈夫曼編碼為:111
最小帶權路徑長度(左圖):WPL=23+31+32+24+25=33
最小帶權路徑長度(右圖):WPL=25+24+23+32+31=33
由此可見,不管是左子樹權值大於右子樹權值還是小於右子樹權值的哈夫曼樹的最小帶權路徑長度不變。
例題(實驗課)
#include<iostream>
using namespace std;
typedef struct BiTNode {
struct BiTNode* lchild, * rchild;
int data;
}BiTNode,*BiTree;
BiTree InsertBST(BiTree& T, int key);
BiTree CreateBST(BiTree& T);
void InOrder(BiTree T);
bool Delete(BiTree& p);
bool DeleteBST(BiTree& T, int key);
BiTNode* SearchBST(BiTNode* &t, int key)
{
if (!t || t->data == key)
return t;
else if (key > t->data)
return (SearchBST(t->rchild, key));
else
return (SearchBST(t->lchild, key));
}
BiTree InsertBST(BiTree &T,int key)
{
if (T == NULL) {
T = new BiTNode;
T->data = key;
T->rchild = T->lchild = NULL;
}
else if (T->data == key) return NULL;
else if (T->data > key) return InsertBST(T->lchild, key);
else return InsertBST(T->rchild, key);
}
BiTree CreateBST(BiTree& T) {
T = NULL;
int m[1000], n;
int key = 0;
cout << "請輸入節點的個數";
cin >> n;
for (int i = 0; i < n; i++) {
cin >> m[i];
}
for (int i = 0; i < n; ++i) {
key = m[i];
InsertBST(T, key);
}
return T;
}
void InOrder(BiTree T) {
if (T != NULL) {
InOrder(T->lchild);
cout << T->data << " ";
InOrder(T->rchild);
}
}
bool Delete(BiTree& p)
{
BiTree q, s;
if (p->rchild == NULL) {
q = p;
p = p->rchild;
delete q;
}
else if (p->lchild == NULL) {
q = p;
p = p->rchild;
delete q;
}
else {
q = p;
s = p->lchild;
while (s->rchild) {
q = s;
s = s->rchild;
}
p->data = s->data;
q->lchild = s->lchild;
delete s;
}
return true;
}
bool DeleteBST(BiTree& T, int key)
{
if (T == NULL)
return false;
else{
if (key < T->data)
DeleteBST(T->lchild, key);
else if (key > T->data)
DeleteBST(T->rchild, key);
else {
return Delete(T);
}
}
}
int main() {
BiTree T;
int key;
CreateBST(T);
InOrder(T);
cout << endl;
cout << "請輸入刪除的關鍵字:";
cin >> key;
DeleteBST(T, key);
InOrder(T);
cout << "請輸入你要查找的節點:";
int x;
cin >> x;
if (SearchBST(T, key))
cout << "存在"<<endl;
else cout << "不存在" << endl;
return 0;
}
難點在於刪除函數,不懂的問題看書本,與同學交流合作,共同解決