霍夫曼編碼

11332 薛丞佑

霍夫曼編碼

  • 霍夫曼編碼(霍夫曼算法)是誰發明的?

霍夫曼啊這還要我講嗎

  • 啊它能幹嘛

能夠做到「無失真資料壓縮」

  • 那是什麼?

  1. 不影響資料內容的情況下
  2. 盡可能壓縮儲存資料的空間

讓我們來煮個栗子

現在我們有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

都是「葉」

➜

我們用剛剛的00,01,10,11來示範

就可以把他變成這樣的一棵二元樹

只要我們把符號全部放在「葉」上,

就不會有前綴碼的問題出現

霍夫曼樹

霍夫曼樹是用來找出霍夫曼編碼的一個過程

在開始種樹之前,先來講講霍夫曼樹的一些小特點好了

  1. 由下往上建構樹
  2. 權重越重的符號在越上面

什麼?你說這些你都不知道?

正常因為我等一下才要教

霍夫曼樹

那怎麼種樹呢?

我們會先得到一組符號的集合
以及一組他們對應的權重的集合
像這樣

符號 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"

Code 模板

#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;
}

講了這麼多,感覺好像有點...空虛?

算法跟模板我都會了

那...實例呢?

講權重跟符號好像不夠親切

  1. 符號: 就是你的項,很好理解
  2. 權重: 通常是符號出現的頻率

因為霍夫曼編碼最常做的事就是符號編寫

吃點飯粒吧

蘿莉切割問題

(秒殺欸,雖然我還不會霍夫曼的時候看了半天)

謝謝大家

Huffman Coding

By ganyumywife

Huffman Coding

  • 485