資料結構與演算法系列 哈夫曼樹及哈夫曼編碼

2021-07-09 02:26:32 字數 4322 閱讀 2504

一:背景
在**哈夫曼前,先來看乙個生活例項:將學生的百分制成績轉為等級制,即

[90 , 100]為a;

[80 , 90)為b;

[70 , 80)為c;

[60 , 70)為d;

[0 , 60)為e。

**實現為:

if (a < 60) 

else if (a < 70)

else if (a < 80)

else if (a < 90)

else

我們把**表現的更形象點,繪出它對應的判別樹:

因為在實際生活中,學生的成績在這5哥等級上的分布是不均勻的,假設其分布規律如下:

分數0-59

60-69

70-79

80-89

90-100

比例0.05

0.15

0.40

0.30

0.1

如果學生的總成績資料有10000條,則5%的資料需 1 次比較,15%的資料需 2 次比較,40%的資料需 3 次比較,40%的資料需 4 次比較,因此 10000 個資料比較的

次數為:  10000 (5%+2×15%+3×40%+4×40%)=31500次

但是如果我們把判別樹稍微修改下,也就是對等級的判斷順序改變下,又如何呢?

此種形狀的二叉樹,需要的比較次數是:10000 (3×20%+2×80%)=22000次,顯然:兩種判別樹的效率是不一樣的。

問題是能不能找到一種效率最高的判別樹呢?

那就是接下來要講的哈夫曼樹。

二:哈夫曼樹
哈夫曼樹(huffman),又叫最優樹,是一類帶權路徑長度最短的樹。

它們的帶權路徑長度分別為:

圖a: wpl=5*2+7*2+2*2+13*2=54

圖b: wpl=5*3+2*3+7*2+13*1=48

可見,圖b的帶權路徑長度較小,我們可以證明圖b就是哈夫曼樹(也稱為最優二叉樹)。

那麼我們如何構造哈夫曼樹?

一般可以按下面步驟構建:

1:將所有左,右子樹都為空的作為根節點。

2:在森林中選出兩棵根節點的權值最小的樹作為一棵新樹的左,右子樹,且置新樹的附加根節點的權值為其左,右子樹上根節點的權值之和。注意,左子樹的權值應小於右子   樹的權值。

3:從森林中刪除這兩棵樹,同時把新樹加入到森林中。

4:重複2,3步驟,直到森林中只有一棵樹為止,此樹便是哈夫曼樹。

下面是構建哈夫曼樹的**過程:

三:哈夫曼編碼
目前。進行快速遠距離通訊的主要手段是電報,即將需傳送的文字轉換長由二進位制的字元組成的字串。例如,假設需傳送的電文為"abaccda",它只有4種字元,只需兩個字元的串便可分辨。假設a,b,c,d的編碼分別是00,01,10和11,則上述7個字元的電文便是「00010010101100」,總長14位,對方接收時,可按兩位一分進行解碼。

當然,在傳送電文時希望電文長度盡可能的短。如果對每個字元設計長度不等的編碼,且讓電文中出現次數較多的字元採用盡可能短的編碼,則傳送的電文總長度便可減少。

如果設計a,b,c,d的編碼是0,00,1,01,則上述7個字元可轉化為長度為9的字串「000011010」。但是,這樣的電文無法翻譯,例如傳送過去的字串中前4個字元的字串「0000」就有多種譯法,或是「aaaa」,或是「aba」,或是「bb」等等。因此,若要設計長短不等的編碼,則必須滿足:任意乙個字元編碼不是另乙個字元編碼的字首,這種編碼稱字首編碼。

可以用二叉樹來設計二進位制的字首編碼,如此得到必是二進位制字首編碼,讀者可以自己證明;同時,我們可以利用設計一棵哈夫曼樹的思想來使電文長度最短。

樹中從根到每個葉子節點都有一條路徑,對路徑上的各分支約定指向左子樹的分支表示」0」碼,指向右子樹的分支表示「1」碼,取每條路徑上的「0」或「1」的序列作為各個葉子節點對應的字元編碼。

就拿上圖例子來說:

a,b,c,d對應的哈夫曼編碼分別為:111,10,110,0

用圖說明如下:

四:哈夫曼編碼實現
由於哈夫曼樹中沒有度為1的結點,則一棵有n個葉子的哈夫曼樹共有2n-1個結點,可以用乙個大小為2n-1 的一維陣列存放哈夫曼樹的各個結點。 由於每個結點同時還包含其雙親資訊和孩子結點的資訊,所以構成乙個靜態三叉鍊錶。

#define _crt_secure_no_deprecate 

#define _crt_secure_cpp_overload_standard_names 1

#include#include//use string

#include//use strcpy

using namespace std;

typedef struct

htnode, *huffmantree;//哈夫曼樹

typedef char** huffmancode;//哈夫曼編碼表

void select(huffmantree & ht, int end, int & s1, int & s2);

void huffmancoding(huffmantree & ht, huffmancode & hc, int * w, char *ch, int n);

void huffmandecoding(huffmantree &ht, string text, int n);

int main()

/* 在ht[0...end]選擇parent為0且weight最小的兩個節點,其序號分別為是s1,s2(s1是最小weight節點的序號)

說白了,就是求出乙個陣列的最小的兩個值的下標

*/void select(huffmantree & ht, int end, int & s1, int & s2)

} }for (int i = 0; i <= end; i++)

}} cout << s1 << " " << s2 << endl;

}/* w存放n個字元的權值(均大於0),構造哈夫曼樹ht,並求出n個字元的哈夫曼編碼hc,ch陣列存放n個字元 */

void huffmancoding(huffmantree & ht, huffmancode & hc, int * w, char * ch, int n)

; for (; i < m; i++, p++)

*p = ;

int s1, s2;

for (i = n; i < m; i++)

//從葉子到根逆向求每個字元的哈夫曼編碼

int start;

hc = new char*[n];

char * cd = new char[n];

cd[n - 1] = '\0';

for (int i = 0; i < n; i++)

hc[i] = new char[n - start];

strcpy(hc[i], &cd[start]);

} deletecd;

}/* 根據編譯出的哈夫曼編碼,譯成字元明文 */

void huffmandecoding(huffmantree &ht, string text, int n)

}}

執行截圖:

資源,參考來自:

返回目錄---->資料結構與演算法目錄

資料結構 哈夫曼樹與哈夫曼編碼

1 路徑 由乙個結點到另乙個結點之間的所有分支共同構成。2 路徑長度 結點之間的分支數目。3 樹的路徑長度 從樹的根結點到其他所有結點的路徑長度之和。4 權 賦予某一實體的值。在資料結構中,實體包括結點和邊,所以對應有結點權和邊權。5 結點的帶權路徑長度 結點與樹的根結點之間的路徑長度與結點權的乘積...

資料結構 哈夫曼樹與哈夫曼編碼

include pch.h include 哈夫曼樹類huffmantree的定義 huffman樹結點類treenode宣告 template class t class huffmannode 建構函式 huffmannode getleft void const void setleft hu...

資料結構 哈夫曼樹 哈夫曼編碼

哈夫曼樹又稱最優樹 二叉樹 是一類帶權路徑最短的樹。構造這種樹的演算法最早是由哈夫曼 huffman 1952年提出,這種樹在資訊檢索中很有用。結點之間的路徑長度 從乙個結點到另乙個結點之間的分支數目。樹的路徑長度 從樹的根到樹中每乙個結點的路徑長度之和。結點的帶權路徑長度 從該結點到樹根之間的路徑...