1. 程式人生 > >資料結構 第14講 神祕電報密碼——哈夫曼編碼

資料結構 第14講 神祕電報密碼——哈夫曼編碼

本文來源於本人著作《趣學演算法》。

看過諜戰電影《風聲》的觀眾都會對影片中神奇的訊息傳遞驚歎不已!吳志國大隊長在受了殘忍的“針刑”之後躺在手術檯上唱空城計,變了音調,把訊息傳給了護士,顧曉夢在衣服上縫補了長短不一的針腳……那麼,片中無處不在的摩爾斯碼到底是什麼?它又有著怎樣的神祕力量呢?

摩爾斯電碼(Morse code)由點dot(. )、劃dash(-)兩種符號組成。它的基本原理是:把英文字母表中的字母、標點符號和空格按照出現的頻率排序,然後用點和劃的組合來代表這些字母、標點符號和空格,使頻率最高的符號具有最短的點劃組合。

圖2-30 神祕電報密碼

2.6.1 問題分析

我們先看一個生活中的例子:

有一群退休的老教授聚會,其中一個老教授帶著剛會說話的漂亮小孫女,於是大家逗她:“你能猜猜我們多大了嗎?猜對了有糖吃哦!”小女孩就開始猜:“你是1歲了嗎?”,老教授搖搖頭。“你是兩歲了嗎?”,老教授仍然搖搖頭。“那一定是3歲了!”……大家哈哈大笑。或許我們都感覺到了小女孩的天真可愛,然而生活中的確有很多類似這樣的判斷。

曾經有這樣一個C++設計題目:將一個班級的成績從百分制轉為等級制。一同學設計的程式為:

if(score <60) cout << "不及格"<<endl;
else if (score <70)
cout << "及格"<<endl; else if (score <80) cout << "中等"<<endl; else if (score <90) cout << "良好"<<endl; else cout << "優秀"<<endl;

在上面程式中,如果分數小於60,我們做1次判定即可;如果分數為60~70,需要判定2次;如果分數為70~80,需要判定3次;如果分數為80~90,需要判定4次;如果分數為90~100,需要判定5次。

這段程式貌似是沒有任何問題,但是我們卻犯了從1歲開始判斷一個老教授年齡的錯誤,因為我們的考試成績往往是呈正態分佈的,如圖2-31所示。

..\17-0245 改圖\0231.tif

圖2-31 執行結果

也就是說,大多數(70%)人的成績要判斷3次或3次以上才能成功,假設班級人數為100人,則判定次數為:

100×10%×1+100×20%×2+100×40%×3+100×20%×4+100×10%×5=300(次)

如果我們改寫程式為:

if(score <80) 
   if (score <70) 
       if (score <60) cout << "不及格"<<endl;
       else cout << "及格"<<endl;
   else cout << "中等"<<endl;
else if (score <90) cout << "良好"<<endl;
   else cout << "優秀"<<endl;

則判定次數為:

100×10%×3+100×20%×3+100×40%×2+100×20%×2+100×10%×2=230(次)

為什麼會有這樣大的差別呢?我們來看兩種判斷方式的樹形圖,如圖2-32所示。

圖2-32 兩種判斷方式的樹形圖

從圖2-32中我們可以看到,當頻率高的分數越靠近樹根(先判斷)時,我們只用1次猜中的可能性越大。

再看五筆字型的編碼方式:

我們在學習五筆時,需要背一級簡碼。所謂一級簡碼,就是指25個漢字,對應著25個按鍵,打1個字母鍵再加1個空格鍵就可打出來相應的字。為什麼要這樣設定呢?因為根據文字統計,這25個漢字是使用頻率最高的。

五筆字根之一級簡碼:

G 一 F 地 D 在  S 要  A 工

H 上 J 是 K 中  L 國  M 同

T 和 R 的 E 有  W 人 Q 我

Y 主 U 產 I 不  O 為  P 這

N 民 B 了 V 發 C 以  X 經

通常的編碼方法有固定長度編碼和不等長度編碼兩種。這是一個設計最優編碼方案的問題,目的是使總碼長度最短。這個問題利用字元的使用頻率來編碼,是不等長編碼方法,使得經常使用的字元編碼較短,不常使用的字元編碼較長。如果採用等長的編碼方案,假設所有字元的編碼都等長,則表示n個不同的字元需logn要位。例如,3個不同的字元a、b、c,至少需要2位二進位制數表示,a為00,b為01,c為10。如果每個字元的使用頻率相等,固定長度編碼是空間效率最高的方法。

不等長編碼方法需要解決兩個關鍵問題:

(1)編碼儘可能短

我們可以讓使用頻率高的字元編碼較短,使用頻率低的編碼較長,這種方法可以提高壓縮率,節省空間,也能提高運算和通訊速度。即頻率越高,編碼越短

(2)不能有二義性

例如,ABCD四個字元如果編碼如下。

A:0。B:1。C:01。D:10。

那麼現在有一列數0110,該怎樣翻譯呢?是翻譯為ABBA,ABD,CBA,還是CD?那麼如何消除二義性呢?解決的辦法是:任何一個字元的編碼不能是另一個字元編碼的字首,即字首碼特性

1952年,數學家D.A.Huffman提出了根據字元在檔案中出現的頻率,用0、1的數字串表示各字元的最佳編碼方式,稱為哈夫曼(Huffman)編碼。哈夫曼編碼很好地解決了上述兩個關鍵問題,被廣泛應用於資料壓縮,尤其是遠距離通訊和大容量資料儲存方面,常用的JPEG圖片就是採用哈夫曼編碼壓縮的。

2.6.2 演算法設計

哈夫曼編碼的基本思想是以字元的使用頻率作為權構建一棵哈夫曼樹,然後利用哈夫曼樹對字元進行編碼。構造一棵哈夫曼樹,是將所要編碼的字元作為葉子結點,該字元在檔案中的使用頻率作為葉子結點的權值,以自底向上的方式,通過n−1次的“合併”運算後構造出的一棵樹,核心思想是權值越大的葉子離根越近。

哈夫曼演算法採取的貪心策略是每次從樹的集合中取出沒有雙親且權值最小的兩棵樹作為左右子樹,構造一棵新樹,新樹根節點的權值為其左右孩子結點權值之和,將新樹插入到樹的集合中,求解步驟如下。

(1)確定合適的資料結構。編寫程式前需要考慮的情況有:

  • 哈夫曼樹中沒有度為1的結點,則一棵有n個葉子結點的哈夫曼樹共有2n−1個結點(n−1次的“合併”,每次產生一個新結點),
  • 構成哈夫曼樹後,為求編碼,需從葉子結點出發走一條從葉子到根的路徑。
  • 譯碼需要從根出發走一條從根到葉子的路徑,那麼我們需要知道每個結點的權值、雙親、左孩子、右孩子和結點的資訊。

(2)初始化。構造n棵結點為n個字元的單結點樹集合T={t1t2t3,…,tn},每棵樹只有一個帶權的根結點,權值為該字元的使用頻率。

(3)如果T中只剩下一棵樹,則哈夫曼樹構造成功,跳到步驟(6)。否則,從集合T中取出沒有雙親且權值最小的兩棵樹titj,將它們合併成一棵新樹zk,新樹的左孩子為ti,右孩子為tjzk的權值為titj的權值之和。

(4)從集合T中刪去titj,加入zk

(5)重複以上(3)~(4)步。

(6)約定左分支上的編碼為“0”,右分支上的編碼為“1”。從葉子結點到根結點逆向求出每個字元的哈夫曼編碼,從根結點到葉子結點路徑上的字元組成的字串為該葉子結點的哈夫曼編碼。演算法結束。

2.6.3 完美圖解

假設我們現在有一些字元和它們的使用頻率(見表2-13),如何得到它們的哈夫曼編碼呢?

表2-13 字元頻率

字元 a b c d e f
頻率 0.05 0.32 0.18 0.07 0.25 0.13

我們可以把每一個字元作為葉子,它們對應的頻率作為其權值,為了比較大小方便,可以對其同時擴大100倍,得到a~f分別對應5、32、18、7、25、13。

(1)初始化。構造n棵結點為n個字元的單結點樹集合T={a,b,c,d,e,f},如圖2-33所示。

..\17-0245 改圖\0233a.tif

圖2-33 葉子結點

(2)從集合T中取出沒有雙親的且權值最小的兩棵樹a和d,將它們合併成一棵新樹t1,新樹的左孩子為a,右孩子為d,新樹的權值為a和d的權值之和為12。新樹的樹根t1加入集合T,a和d從集合T中刪除,如圖2-34所示。

..\17-0245 圖\0241.tif

圖2-34 構建新樹

(3)從集合T中取出沒有雙親的且權值最小的兩棵樹t1和f,將它們合併成一棵新樹t2,新樹的左孩子為t1,右孩子為f,新樹的權值為t1和f的權值之和為25。新樹的樹根t2加入集合T,將t1和f從集合T中刪除,如圖2-35所示。

圖2-35 構建新樹

(4)從集合T中取出沒有雙親且權值最小的兩棵樹c和e,將它們合併成一棵新樹t3,新樹的左孩子為c,右孩子為e,新樹的權值為c和e的權值之和為43。新樹的樹根t3加入集合T,將c和e從集合T中刪除,如圖2-36所示。

..\17-0245 圖\0243.tif

圖2-36 構建新樹

(5)從集合T中取出沒有雙親且權值最小的兩棵樹t2和b,將它們合併成一棵新樹t4,新樹的左孩子為t2,右孩子為b,新樹的權值為t2和b的權值之和為57。新樹的樹根t4加入集合T,將t2和b從集合T中刪除,如圖2-37所示。

..\17-0245 圖\0244.tif

圖2-37 構建新樹

(6)從集合T中取出沒有雙親且權值最小的兩棵樹t3t4,將它們合併成一棵新樹t5,新樹的左孩子為t4,右孩子為t3,新樹的權值為t3t4的權值之和為 100。新樹的樹根t5加入集合T,將t3t4從集合T中刪除,如圖 2-38所示。

圖2-38 哈夫曼樹

(7)T中只剩下一棵樹,哈夫曼樹構造成功。

(8)約定左分支上的編碼為“0”,右分支上的編碼為“1”。從葉子結點到根結點逆向求出每個字元的哈夫曼編碼,從根結點到葉子結點路徑上的字元組成的字串為該葉子結點的哈夫曼編碼,如圖2-39所示。

圖2-39 哈夫曼編碼

2.6.4 虛擬碼詳解

在構造哈夫曼樹的過程中,首先給每個結點的雙親、左孩子、右孩子初始化為−1,找出所有結點中雙親為−1、權值最小的兩個結點t1t2,併合併為一棵二叉樹,更新資訊(雙親結點的權值為t1t2權值之和,其左孩子為權值最小的結點t1,右孩子為次小的結點t2t1t2的雙親為雙親結點的編號)。重複此過程,構造一棵哈夫曼樹。

(1)資料結構

每個結點的結構包括權值、雙親、左孩子、右孩子、結點字元資訊這 5 個域。如圖 2-40所示,定義為結構體形式,定義結點結構體HnodeType

typedef struct
{
     double weight; //權值
     int parent;  //雙親
     int lchild;  //左孩子
     int rchild;  //右孩子
     char value; //該節點表示的字元
} HNodeType;

圖2-40 結點結構體

在編碼結構體中,bit[]存放結點的編碼,start 記錄編碼開始下標,逆向譯碼(從葉子到根,想一想為什麼不從根到葉子呢?)。儲存時,startn−1開始依次遞減,從後向前儲存;讀取時,從start+1開始到n−1,從前向後輸出,即為該字元的編碼。如圖2-41所示。

圖2-41 編碼陣列

編碼結構體HcodeType

typedef struct
{
     int bit[MAXBIT]; //儲存編碼的陣列
     int start;       //編碼開始下標
} HCodeType;          /* 編碼結構體 */

(2)初始化

初始化存放哈夫曼樹陣列HuffNode[]中的結點(見表2-14):

for (i=0; i<2*n-1; i++){
     HuffNode[i].weight = 0;//權值
     HuffNode[i].parent =-1; //雙親
     HuffNode[i].lchild =-1; //左孩子
     HuffNode[i].rchild =-1; //右孩子
}

相關推薦

資料結構 14 神祕電報密碼——編碼

本文來源於本人著作《趣學演算法》。 看過諜戰電影《風聲》的觀眾都會對影片中神奇的訊息傳遞驚歎不已!吳志國大隊長在受了殘忍的“針刑”之後躺在手術檯上唱空城計,變了音調,把訊息傳給了護士,顧曉夢在衣服上縫補了長短不一的針腳……那麼,片中無處不在的摩爾斯碼到底是什麼?它又有著怎樣的神祕

資料結構實驗之二叉樹六:編碼

SDUT oj 2127 這道題是典型的最優二叉樹問題 像下面的程式碼一樣,看水過去了吧 #include <vector> #include <cstdio> #incl

資料結構之二叉樹應用(樹及編碼實現)(C++)

一、哈夫曼樹1.書上用的是靜態連結串列實現,本文中的哈夫曼樹用 排序連結串列 實現;2.實現了從 字元頻率統計、構建權值集合、建立哈夫曼樹、生成哈夫曼編碼,最後對 給定字串的編碼、解碼功能。3.使用到的 “SortedList.h”標頭檔案,在上篇博文:資料結構之排序單鏈表。

#資料結構與演算法學習筆記#PTA17:樹與編碼 Huffman Tree & Huffman Code(C/C++)

2018.5.16 最近一段時間忙於實驗室各種專案和輔導員的各種雜活,間隔了半周沒有耐下心學習。導師最近接了一個要PK京東方的專案讓我來做總負責,確實是很驚喜了。責任心告訴我不能把工作做水了,但是還是嘗試把實權移交給師兄們比較好。 這道題可以說是樹這塊的壓軸題了,無論是程

六週作業1——利用編碼英文字母表

作業要求: 對教材P167中習題5.18,思考並完成問題a-d。(原書PDF下載地址:) 習題如下: 解: (a):  畫出這些字母的最優二叉樹:(根節點值為101不是100,應該是空格的出現頻率

資料結構 17 溝通無限校園網——最小生成樹(kruskal演算法)

本內容來源於本人著作《趣學演算法》,線上章節:http://www.epubit.com.cn/book/details/4825 構造最小生成樹還有一種演算法,Kruskal演算法:設G=(V,E)是無向連通帶權圖,V={1

資料結構 16 溝通無限校園網——最小生成樹(prim演算法)

本內容來源於本人著作《趣學演算法》,線上章節:http://www.epubit.com.cn/book/details/4825 校園網是為學校師生提供資源共享、資訊交流和協同工作的計算機網路。校園網是一個寬頻、具有互動功能和專業性很強的區域網絡。如果一所學校包括多個學院及部門,也可

資料結構——第二章樹和森林:04樹與編碼

1.結點的路徑長度:從根結點到該結點的路徑上分支的數目。 2.樹的路徑長度:樹中每個結點的路徑長度之和。 3.樹的帶權路徑長度:樹中所有葉子結點的帶權路徑長度之和WPL(T) = ∑wklk(對所有葉子結點) 4.最優樹:在所有含n個結點,並帶相同權值的m叉樹中,必存在一棵其帶權路徑長度取最小值的樹,稱

資料結構實驗之二叉樹六:編碼(SDUT 3345)

題解:離散中的“最小生成樹(最優樹)”。 #include <bits/stdc++.h> using namespace std; void qusort(int l, int r, int a[]) { int x = a[l]; int i = l, j =

資料結構————檔案壓縮(利用編碼實現)

檔案壓縮原理: 首先檔案壓縮是通過HuffmaCode實現的、整體思路通過讀取檔案獲取字元出現頻率,通過字元出現頻率可以構建HuffmanTree,每個檔案中出現的字元通過HuffmanTree獲取HuffmanCode,從而將檔案中的字元同過HuffmanTree獲取相應編碼,並寫入壓

資料結構編碼

哈夫曼編/譯碼器程式碼參考連結: (1)資料結構 基於哈夫曼編碼的通訊系統的設計與實現 https://zhidao.baidu.com/question/130785002.html?qbl=relate_question_3&word=���ù�������������ͨ�ſ��

172322 2018-2019-1 《程式設計與資料結構編碼測試報告

172322 2018-2019-1 《程式設計與資料結構》哈夫曼編碼測試報告 課程:《程式設計與資料結構》 班級: 1723 姓名: 張昊然 學號:20172322 教師:王志強 助教:張之睿/張師瑜 編碼測試日期:2018年11月19日 必修/選修: 必修 哈夫

資料結構樹及編碼

哈夫曼樹 給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。 樹節點間的邊相關的數叫做權。 從樹

資料結構與演算法 (七) 樹(Huffman)與編碼

1.演算法思想          哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的路徑長度(若根結點為0層,葉結點到根結點的路徑長度為葉結點的層數)。樹的路徑長度是從樹根到每

SDUTOJ3345資料結構實驗之二叉樹六:編碼

資料結構實驗之二叉樹六:哈夫曼編碼 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Problem Description 字元的編碼方式有多種,除了大家熟悉的ASC

資料結構知識整理 - 樹與編碼

主要內容 基本概念 構造思路 儲存結構 構造演算法 哈夫曼編碼的引入 求哈夫曼編碼   基本概念 1)路徑:由一個結點到另一個結點之間的所有分支共同構成。 2)路徑長度:結點之間的分支數目。 3)樹的路徑長度:從樹的根

資料結構-2-樹與編碼 原理詳解

 首先,介紹下什麼是哈夫曼樹。哈夫曼樹又稱最優二叉樹, 是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點 的權值乘上其到根結點的 路徑長度(若根結點為0層,葉結點到根結點的路徑長度 為葉結點的層數)。樹的帶權路徑長度記為WPL= (W1*L1+W

資料結構課程設計-編碼譯碼

//******************************************** //程式功能:哈夫曼編碼及譯碼 // //日期:2014年11月18 // //******************************************** #incl

資料結構(15)--樹以及編碼的實現

參考書籍:資料結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社 1.哈夫曼樹     假設有n個權值{w1, w2, ..., wn},試構造一棵含有n個葉子結點的二叉樹,每個葉子節點帶權威wi,則其中帶權路徑長度WPL最小的二叉樹叫做最優二叉樹或者哈夫曼樹。     特點:

資料結構——編碼譯碼器

題目5: 哈夫曼編/譯碼器 [問題描述] 利用哈夫曼編碼進行通訊可以大大提高通道利用率,縮簡訊息傳輸時間,降低傳輸成本。但是,這要求在傳送端通過一個編碼系統對待傳資料預先編碼,在接收端將傳來的資料進行譯碼(復原)。對於雙工通道(即可以雙向傳輸資訊的通道),每端都需要一個完整的編/