數據結構應用 - 二叉樹
1.表達式樹
描述:表達式樹的葉節點為操作數,其他節點為運算符。
對表達式式樹采用不同的遍歷策略可以分別得到前中後綴三種表達式。
先序遍歷:前綴表達式(不常用)
中序遍歷:中綴表達式
後序遍歷:後綴表達式
構造表達式樹:把後綴表達式轉化為表達式樹(中綴轉後綴已經在棧的應用中提到過),本質上還是借助了棧。
類似後綴表達式求值,從頭開始逐字符讀入表達式,遇到操作數則建立一個單節點樹,將其指針壓入棧中,當遇到運算符時,將棧頂的兩個指針彈出並作為當前運算符的子節點構成一棵二叉樹,將該樹的指針壓入棧中。
讀到表達式末尾時,留在棧中的只剩下指向最終的表達式樹的指針。
2.編碼樹
編碼:將信息轉化為二進制碼傳輸的過程就是編碼。
解碼:將接受到的二進制碼恢復為原信息就是解碼。
編碼表:字符集中的任意字符都能在編碼表中找到唯一對應的二進制串。字符集到編碼表是單射。
解碼歧義:編碼可以做到一一對應,解碼卻未必。比如,規定S->11,M->111,那麽現有二進制串“111111”,這個二進制串應該解碼為SSS還是MM呢?這就產生了歧義。
產生歧義的根源在於,編碼表中的某些編碼,是其他編碼的前綴。在上例中,S對應的11就是M對應的111的前綴。
前綴無歧義編碼(PFC):既然知道了產生歧義的根源,就可以針對此根源來避免歧義。避免歧義的本質要求就是,保證字符集中的每一個字符所對應的二進制串不是編碼表中其他任何二進制串的前綴。
二叉編碼樹:用二叉樹來描述編碼方案。我們知道從二叉樹的根節點到任一其他節點的通路是唯一的,那麽如果,我們使每一個節點之間的通路都表示二進制碼0和1(左通路0,右通路1),這樣從根節點出發到某節點的通路就變成了一個唯一的二進制串。
↑一棵普通的二叉編碼樹,來自《數據結構(C++語言版)》 鄧俊輝
PFC編碼樹:由上圖可以清晰地看出,S所對應的二進制碼之所以會成為M(所對應的二進制碼)的前綴,是因為S是M的子節點。
換而言之,只要字符集中的字符在編碼樹中存在父子節點的關系,就一定會產生歧義。反過來說,只要在編碼樹中,字符集中的任一字符都不是字符集中其他某字符的子節點,也就是所有字符都是編碼樹的葉節點,就一定不會產生歧義。如下圖。
這就是一棵PFC編碼樹。
PFC編碼樹的解碼:只要根據01子串找到其代表的唯一通路就能找到對應的子節點中的信息。這種解碼不必等二進制碼完全接收後開始,接收過程中即可開始解碼。
3.Huffman樹
先回顧一下幾個關於節點的定義。
節點的權:權值是樹或者圖中兩個結點路徑上的值,這個值表明一種代價,如從一個結點到達另外一個結點的路徑的長度、所花費的時間、付出的費用等。
Huffman樹中的權值可以理解為:權值大表明出現概率大。
節點的帶權路徑長度:該節點的權值與該節點到根節點的路徑的長度的乘積。
樹的帶權路徑長度(WPL):所有葉節點的帶權路徑長度之和。
至此可以給出Huffman樹的定義:在權為w1,w2,…,wn的n個葉子結點的所有二叉樹中,帶權路徑長度WPL最小的二叉樹稱為Huffman樹或最優二叉樹。
Huffman樹的構造:(來自百度百科)
假設有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設為 w1、w2、…、wn,則哈夫曼樹的構造規則為:
(1) 將w1、w2、…,wn看成是有n 棵樹的森林(每棵樹僅有一個結點);
(2) 在森林中選出兩個根結點的權值最小的樹合並,作為一棵新樹的左、右子樹,且新樹的根結點權值為其左、右子樹根結點權值之和;
(3)從森林中刪除選取的兩棵樹,並將新樹加入森林;
(4)重復(2)、(3)步,直到森林中只剩一棵樹為止,該樹即為所求得的哈夫曼樹。
Huffman樹的一些註意點:原博地址Huffman編碼樹:在所有組成節點的權值相同的二叉樹中,Huffman樹的帶權路徑長度最短,利用這一性質,可以縮小傳輸信息時的二進制碼的長度。 將字符在字符串中出現的概率(頻率)作為節點的權。 構造Huffman編碼樹:有以下幾個要點 (1)求出每種字符在字符串中出現的概率(權) (2)由於需要不斷生成新樹刪除舊樹,借助輔助向量存放 頻率統計:1、滿二叉樹不一定是哈夫曼樹
2、哈夫曼樹中權越大的葉子離根越近 (很好理解,WPL最小的二叉樹)
3、具有相同帶權結點的哈夫曼樹不惟一
4、哈夫曼樹的結點的度數為 0 或 2, 沒有度為 1 的結點。
5、包含 n 個葉子結點的哈夫曼樹中共有 2n – 1 個結點。
6、包含 n 棵樹的森林要經過 n–1 次合並才能形成哈夫曼樹,共產生 n–1 個新結點
1 int* statistics ( char* sample_text_file ) { //統計字符出現頻率 2 int* freq = new int[N_CHAR]; //以下統計需隨機訪問,故以數組記錄各字符出現次數 3 memset ( freq, 0, sizeof ( int ) * N_CHAR ); //清零 4 FILE* fp = fopen ( sample_text_file, "r" ); //assert: 文件存在且可正確打開 5 for ( char ch; 0 < fscanf ( fp, "%c", &ch ); ) //逐個掃描樣本文件中的每個字符 6 if ( ch >= 0x20 ) freq[ch - 0x20]++; //累計對應的出現次數 7 fclose ( fp ); return freq; 8 }
初始化Huffman森林:
將求得的頻率對應到每個節點。
1 HuffForest* initForest ( int* freq ) { //根據頻率統計表,為每個字符創建一棵樹 2 HuffForest* forest = new HuffForest; //以List實現的Huffman森林 3 for ( int i = 0; i < N_CHAR; i++ ) { //為每個字符 4 forest->insertAsLast ( new HuffTree ); //生成一棵樹,並將字符及其頻率 5 forest->last()->data->insertAsRoot ( HuffChar ( 0x20 + i, freq[i] ) ); //存入其中 6 } 7 return forest; 8 }
構造:
時間復雜度O(n2)
1 HuffTree* minHChar ( HuffForest* forest ) { //在Huffman森林中找出權重最小的(超)字符 2 ListNodePosi ( HuffTree* ) p = forest->first(); //從首節點出發查找 3 ListNodePosi ( HuffTree* ) minChar = p; //最小Huffman樹所在的節點位置 4 int minWeight = p->data->root()->data.weight; //目前的最小權重 5 while ( forest->valid ( p = p->succ ) ) //遍歷所有節點 6 if ( minWeight > p->data->root()->data.weight ) //若當前節點所含樹更小,則 7 { minWeight = p->data->root()->data.weight; minChar = p; } //更新記錄 8 return forest->remove ( minChar ); //將挑選出的Huffman樹從森林中摘除,並返回 9 } 10 11 HuffTree* generateTree ( HuffForest* forest ) { //Huffman編碼算法 12 while ( 1 < forest->size() ) { 13 HuffTree* T1 = minHChar ( forest ); HuffTree* T2 = minHChar ( forest ); 14 HuffTree* S = new HuffTree(); 15 S->insertAsRoot ( HuffChar ( ‘^‘, T1->root()->data.weight + T2->root()->data.weight ) ); 16 S->attachAsLC ( S->root(), T1 ); S->attachAsRC ( S->root(), T2 ); 17 forest->insertAsLast ( S ); /*DSA*/ //print(forest); 18 } //assert: 循環結束時,森林中唯一(列表首節點中)的那棵樹即Huffman編碼樹 19 return forest->first()->data; 20 }
由Huffman樹生成編碼表:
1 static void //通過遍歷獲取各字符的編碼 2 generateCT ( Bitmap* code, int length, HuffTable* table, BinNodePosi ( HuffChar ) v ) { 3 if ( IsLeaf ( *v ) ) //若是葉節點(還有多種方法可以判斷) 4 { table->put ( v->data.ch, code->bits2string ( length ) ); return; } 5 if ( HasLChild ( *v ) ) //Left = 0 6 { code->clear ( length ); generateCT ( code, length + 1, table, v->lc ); } 7 if ( HasRChild ( *v ) ) //Right = 1 8 { code->set ( length ); generateCT ( code, length + 1, table, v->rc ); } 9 } 10 11 HuffTable* generateTable ( HuffTree* tree ) { //將各字符編碼統一存入以散列表實現的編碼表中 12 HuffTable* table = new HuffTable; Bitmap* code = new Bitmap; 13 generateCT ( code, 0, table, tree->root() ); release ( code ); return table; 14 };
參考資料【1】《數據結構(C++語言版)》 鄧俊輝
【2】《數據結構與算法分析——C語言描述》 Mark Allen Weiss
【3】《大話數據結構》 程傑
數據結構應用 - 二叉樹