11332 薛丞佑
霍夫曼啊這還要我講嗎
能夠做到「無失真資料壓縮」
現在我們有A,B,C,D
四個東西
我們要給他們編碼
符號 | A | B | C | D |
---|---|---|---|---|
編碼 | 0000 | 0001 | 0010 | 0011 |
用電腦的二元碼方式來表示的話
可以是0000,0001,0010,0011
讓我們把它從這樣
盡可能減少儲存的空間
變成這樣
符號 | A | B | C | D |
---|---|---|---|---|
編碼 | 0000 | 0001 | 0010 | 0011 |
符號 | A | B | C | D |
---|---|---|---|---|
編碼 | 0 | 01 | 10 | 1 |
看起來我們完美達成了目的呢
真的是這樣嗎?
怎麼可能,這麼簡單的話我還教什麼
假如今天我收到一串編碼
“0010110” 那他會被破解成怎樣?
先幫你們把表格搬過來
符號 | A | B | C | D |
---|---|---|---|---|
編碼 | 0 | 01 | 10 | 1 |
ABBC?
還是AACDC?
還是AADADDA?
可讀性被破壞了!
當其中一個編碼是前一個的「前綴」時,
就會出現無法判斷的情況。
問題出在
符號 | A | B | C | D |
---|---|---|---|---|
編碼 | 0 | 01 | 10 | 1 |
例如 A(0) 是 B(01) 的前綴
對一組數據找到一組能壓縮空間,
又不會出現「前綴」問題的編碼!
但是,一旦要存取的資料多了起來
這樣也還是太長了
那就來找出霍夫曼編碼吧
講了這麼多終於要進重點了
使用「樹」的力量!
等等,應該是這樣
使用「樹」的力量!
好像也不太準確
使用「樹」的力量!
這才對嘛
來點仙貝芝士好了
樹的定義︰沒有環的連通圖
對這張圖來說
以A點為根
對B點來說
A是他的父節點
D,E是他的子節點
➜
來點仙貝芝士好了
樹的定義︰沒有環的連通圖
而沒有子節點的節點則被稱為「葉」
什麼?你說你被繞暈了?
以這張圖為例
以A點為根
H,J,K,L,M,G
都是「葉」
➜
更多詳情請閱
只要我們把符號全部放在「葉」上,
就不會有前綴碼的問題出現
霍夫曼樹是用來找出霍夫曼編碼的一個過程
在開始種樹之前,先來講講霍夫曼樹的一些小特點好了
什麼?你說這些你都不知道?
正常因為我等一下才要教
那怎麼種樹呢?
我們會先得到一組符號的集合
以及一組他們對應的權重的集合
像這樣
符號 | A | B | C | D | E |
---|---|---|---|---|---|
權重 | 2 | 3 | 4 | 5 | 6 |
而接下來,我們要做的是
把所有的符號放在「葉」上
還記得嗎,我們會由下往上建構樹
A
E
D
C
B
2
6
5
4
3
而我們通常會把符號按照權重由小排到大,
方便我們種樹
再來,我們要挑選權重最小的兩個葉,
為他們畫上父節點
F
5
而這個父節點的權重,會是他的兩個子節點的和
A
E
D
C
B
2
6
5
4
3
那讓我們繼續做下去
F
5
G
9
H
11
I
20
A
E
D
C
B
2
6
5
4
3
恭喜你,霍夫曼樹畫完了!
F
5
G
9
H
11
I
20
A
E
D
C
B
2
6
5
4
3
接下來,我們要做的就是
F
5
G
9
H
11
I
20
幫他們編碼!
A
E
D
C
B
2
6
5
4
3
為了觀感我們先暫時請權重們離場啊
F
5
G
9
H
11
I
20
A
E
D
C
B
2
6
5
4
3
為了觀感我們先暫時請權重們離場啊
F
G
H
I
A
E
D
C
B
接下來,以左子連結為0,右子連結為1
F
G
H
I
0
1
0
0
1
1
1
0
A
E
D
C
B
那麼,我們就會得到以下這個編碼
符號 | A | B | C | D | E |
---|---|---|---|---|---|
編碼 | 000 | 001 | 01 | 10 | 11 |
你會發現,我們已經解決了前綴碼的問題了
假如今天我收到 “0010110”
我們可以很明確地說出這串編碼的意思是
BCD
那麼,我們就會得到以下這個編碼
符號 | A | B | C | D | E |
---|---|---|---|---|---|
編碼 | 000 | 001 | 01 | 10 | 11 |
在眼尖一點你會發現,
權重越重的符號編碼越短
超級人性化欸,超蚌
那用樹的方式解解看呢?
A B C D E
F
G
H
I
0
1
0
0
1
1
1
0
0010110
A
E
D
C
B
那用樹的方式解解看呢?
A B C D E
F
G
H
I
0
1
0
0
1
1
1
0
0010110
A B C D E
A
E
D
C
B
那用樹的方式解解看呢?
A B C D E
F
G
H
I
0
1
0
0
1
1
1
0
0010110
B
A B C D E
A
E
D
C
B
那用樹的方式解解看呢?
F
G
H
I
0
1
0
0
1
1
1
0
0010110
B
A B C D E
A
E
D
C
B
那用樹的方式解解看呢?
F
G
H
I
0
1
0
0
1
1
1
0
0010110
B
C
A B C D E
A
E
D
C
B
那用樹的方式解解看呢?
F
G
H
I
0
1
0
0
1
1
1
0
0010110
B
C
A B C D E
A
E
D
C
B
那用樹的方式解解看呢?
F
G
H
I
0
1
0
0
1
1
1
0
0010110
B
C
D
我們成功了!
A B C D E
A
E
D
C
B
符號 | T | R | O | M | W |
---|---|---|---|---|---|
權重 | 3 | 10 | 8 | 18 | 9 |
做做看霍夫曼樹
順便破解一下“00000101001111100110”吧
下一頁是Ans
樹應該長這樣
T
M
R
W
O
3
8
9
10
18
11
19
29
48
樹應該長這樣
T
M
R
W
O
0
0
0
1
1
1
0
1
符號 | T | R | O | M | W |
---|---|---|---|---|---|
編碼 | 000 | 11 | 001 | 01 | 10 |
而“00000101001111100110”的答案是…
"TOMORROW"
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int M = 6; // 要編碼的符號個數
typedef struct Tree
{
int freq; // 權重
char key; // 符號
Tree left; // 左子節點
Tree right; // 右子節點
Tree(int fr = 0, char k = '\0', Tree l = null, Tree r = null):
freq(fr), key(k), left(l), right(r) {};
}Tree, pTree;
struct cmp
{
bool operator() (Tree a, Tree b)
{
return a.freq > b.freq; // 升序排列
}
};
priority_queue<pTree, vector<pTree>, cmp> pque; // 用Priority_queue按照權重從小排到大
// 利用中序遍歷的方法輸出霍夫曼編碼
// 左0右1,用st紀錄一個符號的編碼,迭代完一次st回退一個字元
void printCode(Tree proot, string st)
{
if (proot == null)
{
return;
}
// 有左節點先往左走,編碼加一個0
if (proot.left)
{
st += '0';
}
printCode(proot.left, st);
if (!proot.left && !proot.right) // 沒有左右節點的是葉結點
{
cout << proot.key,"'s code: ";
for (size_t i = 0; i < st.size(); ++i)
{
cout << (st[i]);
}
cout << ("\n");
}
st.pop_back(); // 退回前一個符號
//有右節點再往右走,編碼加一個0
if (proot.right)
{
st += '1';
}
printCode(proot.right, st);
}
// 清空優先佇列上分配的記憶體空間
void del(Tree proot)
{
if (proot == null)
{
return;
}
del(proot.left);
del(proot.right);
delete proot;
}
// 霍夫曼編碼
void huffman()
{
int i;
char c;
int fr;
// 讀入測試資料
for (i = 0; i < M; ++i)
{
Tree pt = new Tree;
cin >> c >> fr;
getchar();
pt.key = c;
pt.freq = fr;
pque.push(pt);
}
//將佇列中的前兩項(權重最小和次小)拿出來相加再放回去,直到只剩下一項
while (pque.size() > 1)
{
Tree proot = new Tree;
pTree pl, pr;
pl = pque.top(); pque.pop();
pr = pque.top(); pque.pop();
proot.freq = pl.freq + pr.freq;
proot.left = pl;
proot.right = pr;
pque.push(proot);
}
string s = "";
printCode(pque.top(), s);
del(pque.top());
}
int main()
{
huffman();
return 0;
}
講了這麼多,感覺好像有點...空虛?
算法跟模板我都會了
那...實例呢?
講權重跟符號好像不夠親切
因為霍夫曼編碼最常做的事就是符號編寫
(秒殺欸,雖然我還不會霍夫曼的時候看了半天)