霍夫曼編碼

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

  • 91