Huffman Encoding
2020-1ํ๊ธฐ, ๋ํ์์ โ์๊ณ ๋ฆฌ์ฆโ ์์ ์ ๋ฃ๊ณ ๊ณต๋ถํ ๋ฐ๋ฅผ ์ ๋ฆฌํ ๊ธ์ ๋๋ค. ์ง์ ์ ์ธ์ ๋ ํ์์ ๋๋ค :)
์์ฝ๊ฒ๋ <Huffman Encoding>์ ๋ํ ๋ฐฑ์ค ๋ฌธ์ ๋ฅผ ์ฐพ์ง ๋ชป ํ๋ค. ๊ทธ๋์ ์์ฒด์ ์ผ๋ก ๋ฌธ์ ์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ๊ฐ๋จํ๊ฒ ๋ง๋ค์ด ์ด๊ฒ์ ๊ธฐ์ค์ผ๋ก ๋ฌธ์๋ฅผ ์์ฑํ๋๋ก ํ๊ฒ ๋ค ๐
Situation
Consider a string consisting of 130 million characters and the alphabet {A, B, C, D}. CDABDBACCDDBBCCDCC...
How to write this long string in binary with most economic way?
* Input
$1 \le N \le 20$ length alphabetical string.
* Output
Most economic binary encoding.
* Testcase 1
# input
AAAABBBCCD
# output
0000101010111111110
* Testcase 2
# input
ABBCCCBBA
# output
10001111110010
โป NOTE: Several optimal encodings may exist.
์ฌ์ค ์๋์ ์ฌ์ดํธ์์ ๋ช๊ฐ์ง ํ ์คํธ์ผ์ด์ค๋ฅผ ์ง์ ๋ง๋ค์ด์ ํ์ธํ ์ ์๋ค.
๐ Huffman Coding
<Huffman Encoding>์ ๋ฌธ์์ด์ ์์ถํ๋ ๋ํ์ ์ธ ์ธ์ฝ๋ฉ ๋ฐฉ์์ด๋ค. ๊ฐ๋ณ ๊ธธ์ด ์ธ์ฝ๋ฉ(variable-length encoding) ๋ฐฉ์์ธ๋ฐ ๊ฐ ๋ฌธ์์ ์ถํ ๋น๋์ ๋ฐ๋ผ ์์ฃผ ์ถํํ๋ ๋ฌธ์๋ผ๋ฉด ๋ ์งง์ ์ฝ๋๋ฅผ ๋ถ์ฌํ๋ค.
๊ณ ์ ๊ธธ์ด ์ธ์ฝ๋ฉ ๋ฐฉ์๊ณผ ๊ฐ๋ณ ๊ธธ์ด ์ธ์ฝ๋ฉ ๋ฐฉ์์ ํ๋ฒ ๋น๊ตํด๋ณด์.
1. ๊ณ ์ ๊ธธ์ด ์ธ์ฝ๋ฉ
A = 00, B = 01, C = 10, D = 11
AAAABBBCCD -> 0000 0000 0101 0110 1011 (20 bits)
2. ๊ฐ๋ณ ๊ธธ์ด ์ธ์ฝ๋ฉ
A = 0, B = 10, C = 111, D = 110
AAAABBBCCD -> 0000 1010 1011 1111 110 (19 bits)
์โฆ ์ฌ์ค ์์ ์์ ์์๋ ๊ทธ๋ ๊ฒ ๋ง์ด ์ค์ง ์์์ง๋ง, ๋ง์ฝ ํน์ ๋ฌธ์์ ์ถํ ๋น๋๊ฐ ๋ค๋ฅธ ๋ฌธ์๋ณด๋ค ์๋์ ์ผ๋ก ๋ง๋ค๋ฉด ๊ฐ๋ณ ๊ธธ์ด ์ธ์ฝ๋ฉ์ ์ข์ ์ ํ์ด ๋ ์ ์๋ค! (์๋ฅผ ๋ค์ด ์์ด์์๋ ๋ชจ์์ ํด๋นํ๋ a, i, u, e, o ๋ฌธ์๊ฐ ์์ฃผ ๋ฑ์ฅํ ๊ฒ์ด๋ค.)
๊ทธ๋ฌ๋ ์ด๋ฐ <๊ฐ๋ณ ๊ธธ์ด ์ธ์ฝ๋ฉ> ๋ฐฉ์์ encoding-decoding์ด ์ ์ผ(unique)ํด์ผ ํ๋ค๋ ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํ๋ค! ์ฐ๋ฆฌ๊ฐ ์ํธํ(encoding) ํ์ง๋ง ์ ํํ ๋ณตํธํ(decoding)ํ์ง ๋ชปํ๋ค๋ฉด ๊ทธ๊ฒ์ ์ ํ ๋์์ด ๋์ง ์๋๋ค. <Huffman Encoding>์์๋ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด prefix-free ๋ฐฉ์์ผ๋ก ๋ฌธ์๋ฅผ ์ธ์ฝ๋ฉ ํ๋ค. ์ด๊ฒ์ ์ด๋ค ์ธ์ฝ๋ฉ ์ฝ๋๋ผ๋ ๋ค๋ฅธ ์ธ์ฝ๋์ prefix๊ฐ ๋์ง ์๋๋ค๋ ๋ง์ด๋ค. ์๋ฅผ ๋ค์ด A๋ฅผ 101์ผ๋ก ์ธ์ฝ๋ฉ ํ๋ค๋ฉด, 1, 10, 101๋ก๋ ์ ๋ ์ธ์ฝ๋ฉ ํ์ง ์๋๋ค๋ ๊ท์น์ด๋ค.
<Huffman Encoding>์ ์๋์ ๊ฐ์ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ต์ ์ ์ธ์ฝ๋ฉ ๋ฐฉ์์ ์ฐพ๋๋ค.
1. ๋ฌธ์์ด์์ ๋ฌธ์๋ค์ ๋น๋์๋ฅผ ๊ณ์ฐํ๋ค: $\{f_i \mid i \in \Omega \}$
2. ๋น๋์ $f_i$๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ฌธ์ ์งํฉ์ ์ ๋ ฌํ๋ค.
3. ์ ๋ ฌ๋ ๋ฌธ์ ๋ฐฐ์ด์์ ๊ฐ์ฅ ์์ ๋น๋๋ฅผ ๊ฐ์ง๋ 2๊ฐ ๋ฌธ์๋ฅผ ๊บผ๋ด ๊ฐ๊ฐ์ left child, right child๋ก ๊ฐ๋ ํธ๋ฆฌ๋ฅผ ์์ฑํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ ํธ๋ฆฌ์ ๋น๋์๋ฅผ ํฉ์น ๋ ธ๋๋ฅผ ๋ค์ ๋ฌธ์ ๋ฐฐ์ด์ ์ฝ์ ํ๋ค.
4. [2-3] ๊ณผ์ ์ ๋ฌธ์ ๋ฐฐ์ด์ ๋จ ํ๋์ ๋ ธ๋๋ง ๋จ์ ๋๊น์ง ๋ฐ๋ณตํ๋ค.
์์ ๊ฐ์ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๊ธฐ ์ํด <์ฐ์ ์์ ํ>๋ฅผ ์ฌ์ฉํ๋ค. ์ด ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ฉด [2] ๊ณผ์ ์ ์ ๋ ฌ์ ์ง์ ์ํํ์ง ์์๋ ๋๋ค! ๊ทธ๋์ ์๊ณ ๋ฆฌ์ฆ์ ๋ค์ ์ฐ๋ฉด,
1. ๋ฌธ์์ด์์ ๋ฌธ์๋ค์ ๋น๋์๋ฅผ ๊ณ์ฐํ๋ค: $\{f_i \mid i \in \Omega \}$
2. insert all $f_i$ into a priority queue $H$
3. repeat this until there left one node in $H$
3-1. $u = \texttt{deletemin}(H)$, $v = \texttt{deletemin}(H)$
3-2. create a node $k$ with children $u$ and $v$ and $f_k = f_u + f_v$
3-3. insert new node into $H$
๊ตฌํ
<Huffman Encoding>์ C++๋ก ๊ตฌํํด๋ณด์!
๋ฌธ์์ด์ ์ ๋ ฅ๋ฐ๊ณ , ๋น๋ ์์ ๋ํ Map์ ์๋์ ๊ฐ์ด ๋ง๋ค ์ ์๋ค.
string ss;
cin >> ss; // Assumption. only uppercase Alphabet
// generate frequency map
map<char, int> freq_map;
for (int i = 0; i < ss.length(); i++) {
if (freq_map[ss[i]]) {
freq_map[ss[i]] += 1;
} else {
freq_map[ss[i]] = 1;
}
}
๋ค์์ Priority Queue $H$์ ๋
ธ๋๋ฅผ ๋ฃ๋ ๊ฒ์ธ๋ฐ, ๊ทธ ์ ์ Node
์๋ฃ๊ตฌ์กฐ๋ฅผ ์ง์ ์ ์ํด์ผ ํ๋ค.
struct Node {
int freq;
char character;
Node *left;
Node *right;
};
๊ทธ๋ฆฌ๊ณ PQ $H$์ ์ฐ๊ธฐ ์ํด ๋น๊ต์ฐ์ฐ์๋ ์ง์ ๊ตฌํํด์ค๋ค.
struct Node_greater {
bool operator()(const Node *left, const Node *right) {
return left->freq > right->freq;
}
};
๊ทธ๋ฆฌ๊ณ PQ $H$๋ฅผ ์์ฑํ๋ฉด,
// insert all nodes into priority queue
priority_queue<Node *, vector<Node *>, Node_greater> pq;
for (auto &freq_pair: freq_map) {
char character = freq_pair.first;
int freq = freq_pair.second;
Node *node = new Node({freq, character, NULL, NULL});
pq.push(node);
}
์ฒ์์๋ priorirty_queue<Node, ...>
๋ก ๊ตฌํํ๋๋ฐ ์ฃผ์ ํ ๋น์์ ๋ญ๊ฐ ๊ผฌ์ด๋ ๋๋์ด ์์ด์ ๋์ ํ ๋น๊ณผ priority_queue<Node *, ...>
๋ฅผ ์ฐ๋ ๋ฐฉํฅ์ผ๋ก ๊ตฌํํ๋ค.
๋ค์์ PQ $H$์์ ๋ ธ๋๋ฅผ 2๊ฐ์ฉ ๊บผ๋ด์ Huffman Tree๋ฅผ ์์ฑํ๋ฉด ๋๋ค.
// generate Huffman tree
while (pq.size() > 1) {
Node *node1 = pq.top();
pq.pop();
Node *node2 = pq.top();
pq.pop();
Node *newNode = new Node({node1->freq + node2->freq, '0', node1, node2});
pq.push(newNode);
}
๊ทธ๋ฌ๋ ์ค์ ๋ก ๋ฌธ์๋ฅผ ์ธ์ฝ๋ฉ ํ๊ธฐ ์ํด์ ์์ ๊ณผ์ ์ผ๋ก ์ป์ PQ $H$์ ์ ์ฅ๋ ๋ง์ง๋ง ๋ ธ๋์ธ root ๋ ธ๋๋ฅผ ์ฌ์ฉํด Huffman Tree๋ฅผ ์ํํด์ผ ํ๋ค. ์ด๊ฒ์ DFS๋ก ์๋์ ๊ฐ์ด ๊ตฌํํ ์ ์๋ค.
void dfs(map<char, string> &encode_map, const Node &node, string encode) {
if (!node.left && !node.right) {
encode_map[node.character] = encode;
} else {
dfs(encode_map, *node.left, encode + "0");
dfs(encode_map, *node.right, encode + "1");
}
}
...
// encoding using Huffman tree!
Node *root = pq.top();
map<char, string> encode_map;
dfs(encode_map, *root, "");
for (auto &encode: encode_map) {
cout << encode.first << ": " << encode.second << "\n";
}
์์ ๊ฐ์ด ์ ๊ตฌํํ๋ฉด ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ๋ค.
# Testcase 1
(A 4) (B 3) (C 2) (D 1)
A: 0
B: 10
C: 111
D: 110
# Testcase 2
ABBCCCBBA
(A 2) (B 4) (C 3)
A: 10
B: 0
C: 11
๋ณธ ํฌ์คํธ๋ <Greedy Algorithm> ์๋ฆฌ์ฆ์ ์ผ๋ถ ์ ๋๋ค. ๋ ๋ง์ <Greedy Algorithm>์ ๋ํด ์ดํด๋ณด๊ณ ์ถ๋ค๋ฉด ์๋์์ ํ์ธํ ์ ์์ต๋๋ค ๐
๐ Greedy Algorithm