2020-1ν•™κΈ°, λŒ€ν•™μ—μ„œ β€˜μ•Œκ³ λ¦¬μ¦˜β€™ μˆ˜μ—…μ„ λ“£κ³  κ³΅λΆ€ν•œ λ°”λ₯Ό μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€. 지적은 μ–Έμ œλ‚˜ ν™˜μ˜μž…λ‹ˆλ‹€ :)

8 minute read

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

Categories:

Updated: