通常的編碼方法有固定長度和不等長度編碼。最優編碼方案的目的是使總碼長度最短。
如果採用等長的編碼方案,假設所有字元的編碼都等長,則表示n個不同的字元需要位,例如三個不同的字元abc,至少需要2位二進位制數表示:a(00)、b(01)、c(10)。如果每個字元的使用頻率相等的話,固定長度編碼是空間效率最高的方法。
那麼問題來了,等長編碼方案,n個不同字元需要幾位來表示呢?log2n取上限。
利用字元的使用頻率來編碼,是不等長編碼方法,使得經常使用的字元編碼較短,不常使用的字元編碼較長。
不等長編碼需要解決兩個關鍵問題:
使用頻率高的字元編碼較短,使用頻率低的編碼較長,可提高壓縮率,節省空間,也能提高運算和通訊速度。
即**頻率越高,編碼越短**。
例如a(0)、b(1)、c(01),那麼傳輸的01001就不知道到底是什麼了,開頭的01,可以說是ab,也可以說是c。這就是二義性。
解決二義性:
任何乙個字元的編碼不能是另乙個字元編碼的字首。
即**字首碼特性**。
2023年,數學家d.a.huffman提出了用字元在檔案**現的頻率來建議乙個用0,1串表示各字元的最佳編碼方式,成為huffman編碼。哈夫曼編碼很好的解決了上述兩個關鍵的問題,廣泛地應用於資料壓縮、尤其是遠距離通訊和大容量資料儲存,常用的jpeg就是採用哈夫曼編碼壓縮的。
哈夫曼編碼的基本思想就是以字元的使用頻率來構建一棵哈夫曼樹,然後利用哈夫曼樹對字元進行編碼。
構建哈夫曼樹,是將所要編碼的字元作為葉子結點,該字元在檔案中使用的頻率作為葉子結點的權值,自底向上的方式,通過n-1次的「合併」運算後構造出來的樹。核心思想是讓權值大的葉子離根最近。
哈夫曼演算法採取的貪心策略是每次從樹的集合中取出沒有雙親且權值最小的兩棵樹作為左右子樹(一般情況下有個約定,兩棵樹中權值最小的為左子樹),構建一棵新樹,新樹根結點的權值為其左右孩子結點權值之和,將新樹插入到樹的集合中。這也是構建哈夫曼樹的步驟。字元a
bcde
f頻率0.05
0.32
0.18
0.07
0.25
0.13
我們把頻率擴大100倍,不會影響結果。
我們從t集合中選沒有雙親且權值最小的兩棵樹作為左右子樹,也就是a和d,那麼ad構建的新樹的根結點權值為5+7=12。將這個新樹t1加入到t集合再進行比較,ad不再進行比較了。
選沒有雙親且權值最小的兩棵樹作為左右子樹,也就是t1,f。那麼t1和f構建的新樹的根結點權值為12+13=25。將這個新樹t2加入到t集合再進行比較,t1和f不再進行比較了。
選沒有雙親且權值最小的兩棵樹作為左右子樹,c的權值18最小了,作為左子樹,而t2和e權值都是25,選誰作為右子樹呢?選e。理由就是選前面的,新構建的樹在t集合的後面。那麼c和e構建的新樹的根結點權值為18+25=43。將這個新樹t3加入到t集合再進行比較,t2和e不再進行比較了。
以此類推…
那麼我們這裡再說乙個哈夫曼樹帶權路徑長度。
wpl=每個葉子的權值*該葉子到根的路徑長度之和。葉子到根的路徑長度為路徑分支數(邊數)。例如結點c到根經過兩個邊,其到根的路徑長度為2。所以上面這個哈夫曼樹的帶權路徑為
18*2+25*2+5*4+7*4+13*+32*2=237
當然了還有一種計算方法,哈夫曼樹的帶權路徑長度之和等於各新生結點的權值之和。
100+43+12+25+57=237
注意的是我們這裡是擴大了100倍的,也就是說相當於有100個字元。那麼如果有106個字元,其哈夫曼樹編碼的長度時多少呢?106*237/100前言
什麼是哈夫曼編碼
利用哈夫曼樹求得的用於通訊的二進位制編碼稱為哈夫曼編碼。樹中從根到每個葉子結點都有一條路徑,對路徑上的各分支約定指向左子樹的分支表示」0」碼,指向右子樹的分支表示「1」碼,取每條路徑上的「0」或「1」的序列作為各個葉子結點對應的字元編碼,即是哈夫曼編碼。
帶權路徑長度可以看做最終編碼的二進位制長度。
a的編碼為:1000
構建哈夫曼樹是乙個填表的過程,哈夫曼編碼是乙個讀表的過程。
下標weight
parent
lchild
rchild
value05
-1-1-1a
132-1-1-1b
218-1-1-1c
37-1-1-1d
425-1-1-1e
513-1-1-1f
6-1-1-1-17
-1-1
-1-18-1
-1-1-19
-1-1
-1-1
10-1
-1-1
-1ad構建的新樹t1的根結點權值為5+7=12。儲存在下標為6。對應下標為6的weight為12,lchild就是a,所以儲存a的下標0,同理rchild儲存的是d的下標3。同時a和d的parent就知道了,也就是下標6。所以每次構建乙個新樹,需要改動表裡5個地方。
以此類推…
下標weight
parent
lchild
rchild
value05
6-1-1a
1329-1
-1b218
8-1-1c
376-1
-1d425
8-1-1e
5137-1
-1f612
7037
25965
843102
49571071
10100-18
9讀表怎麼讀呢?比如我們想知道a的編碼,我們讀表知道a的parent是6,6的lchild為0正好是a的下標,也就是說a是左分支,所以這個分支編碼為0。接著找6的parent,以此類推,直到parent為-1。這也是從葉子a到根的過程,儲存路徑各分支的編碼,從根到葉子a的編碼序列即為a的編碼。(找是從葉子到根,但是找完了讀還是從根到葉子。)
typedef struct
hcodetype; /* 編碼結構體 */
這裡每個葉子結點都使用了乙個定長陣列,是比較浪費空間的,可以動態建立。
全部**:
#include#include#includeusing namespace std;
#define maxbit 100
#define maxvalue 10000
#define maxleaf 30
#define maxnode maxleaf*2 -1
typedef struct
hnodetype; /* 結點結構體 */
typedef struct
hcodetype; /* 編碼結構體 */
hnodetype huffnode[maxnode]; /* 定義乙個結點結構體陣列 */
hcodetype huffcode[maxleaf];/* 定義乙個編碼結構體陣列*/
/* 構造哈夫曼樹 */
void huffmantree (hnodetype huffnode[maxnode], int n)
/* 輸入 n 個葉子結點的權值 */
for (i=0; i>huffnode[i].value>>huffnode[i].weight;
}/* 構造 huffman 樹 */
for (i=0; i>n;
huffmantree(huffnode,n); //構造哈夫曼樹
huffmancode(huffcode,n); // 哈夫曼樹編碼
//輸出已儲存好的所有存在編碼的哈夫曼編碼
for(i=0;i文章同步在哈夫曼樹和哈夫曼編碼
哈夫曼編碼 哈夫曼樹
1.定義 哈夫曼編碼主要用於資料壓縮。哈夫曼編碼是一種可變長編碼。該編碼將出現頻率高的字元,使用短編碼 將出現頻率低的字元,使用長編碼。變長編碼的主要問題是,必須實現非字首編碼,即在乙個字符集中,任何乙個字元的編碼都不是另乙個字元編碼的字首。如 0 10就是非字首編碼,而0 01不是非字首編碼。2....
哈夫曼樹 哈夫曼編碼
定義從a結點到b結點所經過的分支序列為從a結點到b結點的路徑 定義從a結點到b結點所進過的分支個數為從a結點到b結點的路徑長度 從二叉樹的根結點到二叉樹中所有結點的路徑長度紙盒為該二叉樹的路徑長度 huffman樹 帶權值路徑長度最小的擴充二叉樹應是權值大的外界點舉例根結點最近的擴充二叉樹,該樹即為...
哈夫曼編碼 哈夫曼樹
哈夫曼樹是乙個利用權值進行優化編碼的乙個比較奇怪的樹,他的實現比較簡單,用途也比較單一。哈夫曼樹的實現,實現要求 通過哈夫曼樹可以保證在編碼過程中不會出現例如 1000和100這樣的編碼規則,否則就會編碼失敗,因為1000和100在某些情況下的編碼會一模一樣。通過哈夫曼樹可以保證權值大的值進行編碼時...