Алгоритм Хаффмана — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (rollbackEdits.php mass rollback)
 
(не показаны 53 промежуточные версии 16 участников)
Строка 1: Строка 1:
 +
'''Алгоритм Хаффмана''' (англ. ''Huffman's algorithm'') — алгоритм [[Задача_об_оптимальном_префиксном_коде_с_сохранением_порядка._Монотонность_точки_разреза | оптимального префиксного кодирования]] алфавита. Был разработан в 1952 году аспирантом Массачусетского технологического института Дэвидом Хаффманом при написании им курсовой работы. Используется во многих программах сжатия данных, например, PKZIP 2, LZH и др.
 +
 
== Определение ==
 
== Определение ==
 +
 
{{Определение  
 
{{Определение  
 
|definition=
 
|definition=
'''Коды''' или '''Алгоритм Хаффмана''' ('''Huffman codes''') — широко распространенный и очень эффективный метод сжатия данных, который, в зависимости от характеристик этих данных, обычно позволяет сэкономить от 20% до 90% объема.}}
+
Пусть <tex>A=\{a_{1},a_{2}, \ldots ,a_{n}\}</tex> — алфавит из <tex>n</tex> различных символов, <tex>W=\{w_{1},w_{2}, \ldots ,w_{n}\}</tex> — соответствующий ему набор положительных целых весов. Тогда набор бинарных кодов <tex>C=\{c_{1},c_{2}, \ldots ,c_{n}\}</tex>, где <tex>c_{i}</tex> является кодом для символа <tex>a_{i}</tex>, такой, что:
Рассматриваются данные, представляющие собой последовательность символов. В жадном алгоритме Хаффмана используется таблица, содержащая частоты появления тех или иных символов. С помощью этой таблицы определяется оптимальное представление каждого символа в виде бинарной строки.  
+
 
 +
:* <tex>c_{i}</tex> не является префиксом для <tex>c_{j}</tex>, при <tex>i \ne j</tex>,
 +
 
 +
:* cумма <tex>\sum\limits_{i \in [1, n]} w_{i}\cdot |c_{i}|</tex> минимальна (<tex>|c_{i}|</tex> — длина кода <tex>c_{i}</tex>),
 +
 
 +
называется '''кодом Хаффмана'''.
 +
}}
 +
 
 +
== Алгоритм построения бинарного кода Хаффмана ==
 +
 
 +
Построение кода Хаффмана сводится к построению соответствующего [[ Двоичная_куча | бинарного дерева]] по следующему алгоритму:
 +
 
 +
# Составим [[Список | список]] кодируемых символов, при этом будем рассматривать один символ как дерево, состоящее из одного элемента c весом, равным частоте появления символа в строке.
 +
# Из списка выберем два узла с наименьшим весом.
 +
# Сформируем новый узел с весом, равным сумме весов выбранных узлов, и присоединим к нему два выбранных узла в качестве детей.
 +
# Добавим к списку только что сформированный узел вместо двух объединенных узлов.
 +
# Если в списке больше одного узла, то повторим пункты со второго по пятый.
 +
 
 +
=== Время работы ===
 +
Если сортировать элементы после каждого суммирования или использовать [[Приоритетные_очереди | приоритетную очередь]], то алгоритм будет работать за время <tex>O(N \log N)</tex>.Такую асимптотику можно [[Алгоритм_Хаффмана_за_O(n) |улучшить до <tex>O(N)</tex>]], используя обычные массивы.
 +
 
 +
=== Пример ===
 +
 
 +
[[Файл:Huffman_abracadabra.jpg|400px|thumb|right|Дерево Хаффмана для слова <tex>abracadabra</tex>]]
 +
 
 +
Закодируем слово <tex>abracadabra</tex>. Тогда алфавит будет <tex>A= \{a, b, r, c, d\} </tex>, а набор весов (частота появления символов алфавита в кодируемом слове) <tex>W=\{5, 2, 2, 1, 1\}</tex>:
 +
 
 +
В дереве Хаффмана будет <tex>5</tex> узлов:
 +
 
 +
{| class="wikitable"
 +
! Узел ||  a  ||  b  ||  r  ||  с  ||  d
 +
|-
 +
| Вес || 5 || 2 || 2 || 1 || 1
 +
|}
 +
 
 +
По алгоритму возьмем два символа с наименьшей частотой {{---}} это <tex>c</tex> и <tex>d</tex>. Сформируем из них новый узел <tex>cd</tex> весом <tex>2</tex> и добавим его к списку узлов:
  
== Построение кода Хаффмана ==
+
{| class="wikitable"
+
! Узел || a || b || r || cd
Хаффман изобрел жадный алгоритм, позволяющий составить оптимальный префиксный код, который получил название код Хаффмана. Доказательство корректности этого алгоритма основывается на свойстве жадного выбора и оптимальной подструктуре. Вместо того чтобы демонстрировать, что эти свойства выполняются, а затем разрабатывать псевдокод, сначала мы представим псевдокод. Это поможет прояснить, как алгоритм осуществляет жадный выбор. В приведенном ниже псевдокоде предполагается, что <tex>C</tex> — множество, состоящее из <tex>n</tex> символов, и что каждый из символов <tex>c\in C</tex> — объект с определенной частотой <tex>f(c)</tex>. В алгоритме строится дерево <tex>T</tex>, соответствующее оптимальному коду, причем построение идет в восходящем направлении. Процесс построения начинается с множества, состоящего из <tex>|C|</tex> листьев, после чего последовательно выполняется <tex>|C|-1</tex> операций "слияния", в результате которых образуется конечное дерево. Для идентификации двух наименее часто встречающихся объектов, подлежащих слиянию, используется очередь с приоритетами <tex>Q</tex>, ключами в которой являются частоты <tex>f</tex>. В результате слияния двух объектов образуется новый объект, частота появления которого является суммой частот объединенных объектов:
+
|-
<br><br>
+
| Вес || 5 || 2 || 2 || 2
'''Huffman(<tex>C</tex>)''' <br>
+
|}
<tex>n \gets |C|</tex>  <br>
+
 
<tex>Q \gets C</tex> <br>
+
Затем опять объединим в один узел два минимальных по весу узла {{---}} <tex>r</tex> и <tex>cd</tex>:
'''for''' <tex>i \gets 1</tex> '''to''' <tex>n - 1</tex> <br>
+
 
:'''do''' Выделить память для узла <tex>z</tex> <br>
+
{| class="wikitable"
::left[<tex> z</tex>]<tex> \gets x \gets</tex> Extract_Min(<tex> Q</tex>)<br>
+
! Узел || a || rcd || b
::right[<tex>z</tex>]<tex>\gets y \gets </tex> Extract_Min(<tex>Q</tex>) <br>
+
|-
::<tex>f[z] \gets f[x]+f[y]</tex>
+
| Вес || 5 || 4 || 2
::Insert(<tex>Q</tex>, <tex>z</tex> ) <br>
+
|}
'''return''' Extract_Min(<tex>Q</tex> ) <tex> \rhd </tex> Возврат корня дерева <br><br>
+
 
=== Пример работы алгоритма ===
+
Еще раз повторим эту же операцию, но для узлов <tex>rcd</tex> и <tex>b</tex>:
[[Файл:Huffman.jpg]]<br>
+
 
На каждом этапе показано содержимое очереди, элементы которой рассортированы в порядке возрастания их частот. На каждом шаге работы алгоритма объединяются два объекта (дерева) с самыми низкими частотами. Листья изображены в виде прямоугольников, в каждом из которых указана буква и соответствующая ей частота. Внутренние узлы представлены кругами, содержащими сумму частот дочерних узлов. Ребро, соединяющее внутренний узел с левым дочерним узлом, имеет метку 0, а ребро, соединяющее его с правым дочерним узлом, — метку 1. Слово кода для буквы образуется последовательностью меток на ребрах, соединяющих корень с листом, представляющим эту букву. По скольку данное множество содержит шесть букв, размер исходной очереди равен 6(часть а рисунка), а для построения дерева требуется пять слияний. Промежуточные этапы изображены в частях б-д. Конечное дерево (е) представляет оптимальный префиксный код. Как уже говорилось, слово кода для буквы — это последовательность меток на пути от корня к листу с этой буквой. В строке 2 инициализируется очередь с приоритетами Q, состоящая из элементов множества С. Цикл for в строках 3-8 поочередно извлекает по два узла, х и у, которые характеризуются в очереди наименьшими частотами, и заменяет их в очереди новым узлом z, представляющим объединение упомянутых выше элементов. Частота появления z вычисляется в строке 7 как сумма частот х и у. Узел х является левым дочерним узлом z, а у — его правым дочерним узлом. (Этот порядок является произвольным; перестановка левого и правого дочерних узлов
+
{| class="wikitable"
приводит к созданию другого кода с той же стоимостью.) После п — 1 объединений в очереди остается один узел — корень дерева кодов, который возвращается в строке 9. При анализе времени работы алгоритма Хаффмана предполагается, что Q реализована как бинарная неубывающая пирамида (см. главу 6). Для множества С, состоящего из п символов, инициализацию очереди Q в строке 2 можно выполнить 464 Часть IV. Усовершенствованные методы разработки и анализа за время О (п) с помощью процедуры Build_Min_Heap из раздела 6.3. Цикл for в строках 3-8 выполняется ровно и — 1 раз, и поскольку для каждой операции над пирамидой требуется время О (lgn), вклад цикла во время работы алгоритма равен O(nlgn). Таким образом, полное время работы процедуры Huffman с входным множеством, состоящим из п символов, равно О (nlgn).  
+
! Узел || brcd || a
 +
|-
 +
| Вес || 6 || 5
 +
|}
 +
 
 +
На последнем шаге объединим два узла {{---}} <tex>brcd</tex> и <tex>a</tex>:
 +
 
 +
{| class="wikitable"
 +
! Узел || abrcd
 +
|-
 +
| Вес || 11
 +
|}
 +
 
 +
Остался один узел, значит, мы пришли к корню дерева Хаффмана (смотри рисунок). Теперь для каждого символа выберем кодовое слово (бинарная последовательность, обозначающая путь по дереву к этому символу от корня):
 +
 
 +
{| class="wikitable"
 +
! Символ || a || b || r || с || d
 +
|-
 +
| Код || 0 || 11 || 101 || 1000 || 1001
 +
|}
 +
 
 +
Таким образом, закодированное слово <tex>abracadabra</tex> будет выглядеть как <tex>01110101000010010111010</tex>. Длина закодированного слова {{---}} <tex>23</tex> бита. Стоит заметить, что если бы мы использовали алгоритм кодирования с одинаковой длиной всех кодовых слов, то закодированное слово заняло бы <tex>33</tex> бита, что существенно больше.
  
 
== Корректность алгоритма Хаффмана ==
 
== Корректность алгоритма Хаффмана ==
+
 
Чтобы доказать корректность жадного алгоритма Huffman, покажем, что в за-
+
Чтобы доказать корректность алгоритма Хаффмана, покажем, что в задаче о построении оптимального префиксного кода проявляются свойства жадного выбора и оптимальной подструктуры. В сформулированной ниже лемме показано соблюдение свойства жадного выбора.
даче о построении оптимального префиксного кода проявляются свойства жадного  
+
 
выбора и оптимальной подструктуры. В сформулированной ниже лемме показано  
+
{{Лемма
соблюдение свойства жадного выбора.  
+
|id=lemma1
Лемма 16.2. Пусть С — алфавит, каждый символ с€С которого встречается с ча-
+
|about=1
стотой / [с]. Пусть х и у — два символа алфавита С с самыми низкими частотами.  
+
|statement=
Тогда для алфавита С существует оптимальный префиксный код, кодовые слова  
+
Пусть <tex>C</tex> — алфавит, каждый символ <tex>c \in C</tex> которого встречается с частотой <tex>f[c]</tex>. Пусть <tex>x</tex> и <tex>y</tex> — два символа алфавита <tex>C</tex> с самыми низкими частотами.
символов х и у в котором имеют одинаковую длину и отличаются лишь последним  
+
 
битом.  
+
Тогда для алфавита <tex>C</tex> существует оптимальный префиксный код, кодовые слова символов <tex>x</tex> и <tex>y</tex> в котором имеют одинаковую максимальную длину и отличаются лишь последним битом.  
Доказательство. Идея доказательства состоит в том, чтобы взять дерево Т, пред-
+
|proof=
ставляющее произвольный оптимальный префиксный код, и преобразовать его  
+
Возьмем дерево <tex>T</tex>, представляющее произвольный оптимальный префиксный код для алфавита <tex>C</tex>. Преобразуем его в дерево, представляющее другой оптимальный префиксный код, в котором символы <tex>x</tex> и <tex>y</tex> — листья с общим родительским узлом, находящиеся на максимальной глубине.
в дерево, представляющее другой оптимальный префиксный код, в котором сим-
+
 
волы х и у являются листьями с общим родительским узлом, причем в новом
+
Пусть символы <tex>a</tex> и <tex>b</tex> имеют общий родительский узел и находятся на максимальной глубине дерева <tex>T</tex>. Предположим, что <tex>f[a] \leqslant f[b]</tex> и <tex>f[x] \leqslant f[y]</tex>. Так как <tex>f[x]</tex> и <tex>f[y]</tex> — две наименьшие частоты, а <tex>f[a]</tex> и <tex>f[b]</tex> — две произвольные частоты, то выполняются отношения <tex>f[x] \leqslant f[a]</tex> и <tex>f[y] \leqslant f[b]</tex>. Пусть дерево <tex>T'</tex> — дерево, полученное из <tex>T</tex> путем перестановки листьев <tex>a</tex> и <tex>x</tex>, а дерево <tex>T''</tex> — дерево полученное из <tex>T'</tex> перестановкой листьев <tex>b</tex> и <tex>y</tex>. Разность стоимостей деревьев <tex>T</tex> и <tex>T'</tex> равна:
дереве эти листья находятся на максимальной глубине.  
+
 
Пусть а и b — два символа, представленные листьями с общим родительским
+
<tex>B(T) - B(T') = \sum\limits_{c \in C} f(c)d_T(c) - \sum\limits_{c \in C} f(c)d_{T'}(c) = (f[a] - f[x])(d_T(a) - d_T(x)),</tex>
узлом, которые находятся на максимальной глубине дерева Т. Предположим без
+
 
потери общности, что / [а] ^ / [Ь] и / [х] ^ / [у]. Поскольку / [х] и / [у] — две  
+
что больше либо равно <tex>0</tex>, так как величины <tex>f[a] - f[x]</tex> и <tex>d_T(a) - d_T(x)</tex> неотрицательны. Величина <tex>f[a] - f[x]</tex> неотрицательна, потому что <tex>x</tex> — лист с минимальной частотой, а величина <tex>d_T(a) - d_T(x)</tex> является неотрицательной, так как лист <tex>a</tex> находится на максимальной глубине в дереве <tex>T</tex>. Точно так же перестановка листьев <tex>y</tex> и <tex>b</tex> не будет приводить к увеличению стоимости. Таким образом, разность <tex>B(T') - B(T'')</tex> тоже будет неотрицательной.
самые маленькие частоты (в указанном порядке), а / [а] и / [6] — две произвольные  
+
 
частоты, то выполняются соотношения f[x]^f [а] и / [у] ^ / [6]. Как показано
+
Таким образом, выполняется неравенство <tex>B(T'') \leqslant B(T)</tex>. С другой стороны, <tex>T</tex> — оптимальное дерево, поэтому должно выполняться неравенство <tex>B(T) \leqslant B(T'')</tex>. Отсюда следует, что <tex>B(T) = B(T'')</tex>. Значит, <tex>T''</tex> — дерево, представляющее оптимальный префиксный код, в котором символы <tex>x</tex> и <tex>y</tex> имеют одинаковую максимальную длину, что и доказывает лемму.
на рис. 16.5, в результате перестановки в дереве Т листьев а и х получается дерево
+
}}
Т", а при последующей перестановке в дереве V листьев Ь и у получается дерево
+
 
Т". Согласно уравнению A6.5), разность стоимостей деревьев Т и Т" равна  
+
{{Лемма
В(Т)-В (Г') = ?/(c)dr (с) - ?/(c)(fev (с) =  
+
|id=lemma2
сес сес = f [x] dT (х) + f [a] dT (а) - f [x] dT, (х) - / [a] dT> (а) = = / [х] dT (х) + / [a] dT (а) - / [х] dT (а) - / [а] dT (x) = = (f[a]-f[x})(dT(a)-dT(x))>0,
+
|about=2
поскольку величины / [а]-/ [х] и йт (aj—dr (x) неотрицательны. Величина / [а]
+
|statement=Пусть дан алфавит <tex>C</tex>, в котором для каждого символа <tex>c \in C</tex> определены частоты <tex>f[c]</tex>. Пусть <tex>x</tex> и <tex>y</tex> — два символа из алфавита <tex>C</tex> с минимальными частотами. Пусть <tex>C'</tex> — алфавит, полученный из алфавита <tex>C</tex> путем удаления символов <tex>x</tex> и <tex>y</tex> и добавления нового символа <tex>z</tex>, так что <tex>C' = C \backslash \{ x, y \} \cup {z}</tex>. По определению частоты <tex>f</tex> в алфавите <tex>C'</tex> совпадают с частотами в алфавите <tex>C</tex>, за исключением частоты <tex>f[z] = f[x] + f[y]</tex>. Пусть <tex>T'</tex> — произвольное дерево, представляющее оптимальный префиксный код для алфавита <tex>C'</tex> Тогда дерево <tex>T</tex>, полученное из дерева <tex>T'</tex> путем замены листа <tex>z</tex> внутренним узлом с дочерними элементами <tex>x</tex> и <tex>y</tex>, представляет оптимальный префиксный код для алфавита <tex>C</tex>.  
- f [х] неотрицательна, потому что х — лист с минимальной частотой, величина  
+
|proof=
(о) — dr (x) неотрицательна, потому что а — лист на максимальной глубине  
+
Сначала покажем, что стоимость <tex>B(T)</tex> дерева <tex>T</tex> может быть выражена через стоимость <tex>B(T')</tex> дерева <tex>T'</tex>. Для каждого символа <tex>c \in C \backslash \{x, y \}</tex> верно <tex>d_T(C) = d_{T'}</tex>, значит, <tex>f[c]d_T(c) = f[c]d_{T'}(c)</tex>. Так как <tex>d_T(x) = d_T(y) = d_{T'} (z) + 1</tex>, то
Глава 16. Жадные алгоритмы 465
+
 
Рис. 16.5. Иллюстрация ключевых этапов доказательства леммы 16.2
+
<tex>f[x]d_T(x) + f[y]d_T(y) = (f[x] + f[y])(d_{T'}(z) + 1) = f[z]d_{T'}(z) + (f[x] + f[y])</tex>
в дереве Т. Аналогично, перестановка листьев у и b не приведет к увеличению  
+
 
стоимости, поэтому величина В (Т") — В (Т") неотрицательна. Таким образом,  
+
из чего следует, что
выполняется неравенство В (Г") ^ В (Т), и поскольку Т — оптимальное дерево,  
+
 
то должно также выполняться неравенство В (Т) < В (Т"), откуда следует, что  
+
<tex> B(T) = B(T') + f[x] + f[y] </tex>
В {Т") = В (Т). Таким образом, Т" оптимальное дерево, в котором х и у —
+
 
находящиеся на максимальной глубине дочерние листья одного и того же узла,  
+
или
что и доказывает лемму. д
+
 
Из леммы 16.2 следует, что процесс построения оптимального дерева путем
+
<tex> B(T') = B(T) - f[x] - f[y] </tex>
объединения узлов без потери общности можно начать с жадного выбора, при
+
 
котором объединению подлежат два символа с наименьшими частотами. Почему
+
Докажем лемму от противного. Предположим, что дерево <tex>T</tex> не представляет оптимальный префиксный код для алфавита <tex>C</tex>. Тогда существует дерево <tex>T''</tex> такое, что <tex>B(T'') < B(T)</tex>. Согласно лемме (1), элементы <tex>x</tex> и <tex>y</tex> можно считать дочерними элементами одного узла. Пусть дерево <tex>T'''</tex> получено из дерева <tex>T''</tex> заменой элементов <tex>x</tex> и <tex>y</tex> листом <tex>z</tex> с частотой <tex>f[z] = f[x] + f[y]</tex>. Тогда
такой выбор будет жадным? Стоимость объединения можно рассматривать как
+
 
сумму частот входящих в него элементов. В упражнении 16.3-3 предлагается
+
<tex>B(T''') = B(T'') - f[x] - f[y] < B(T) - f[x] - f[y] = B(T')</tex>,
показать, что полная стоимость сконструированного таким образом дерева равна
+
 
сумме стоимостей его составляющих. Из всевозможных вариантов объединения
+
что противоречит предположению о том, что дерево <tex>T'</tex> представляет оптимальный префиксный код для алфавита <tex>C'</tex>. Значит, наше предположение о том, что дерево <tex>T</tex> не представляет оптимальный префиксный код для алфавита <tex>C</tex>, неверно, что и доказывает лемму.
на каждом этапе в процедуре Huffman выбирается тот, в котором получается
+
}}
минимальная стоимость.
+
 
В приведенной ниже лемме показано, что задача о составлении оптимальных
 
префиксных кодов обладает свойством оптимальной подструктуры.
 
Лемма 16.3. Пусть дан алфавит С, в котором для каждого символа се С опре-
 
делены частоты / [с]. Пусть х иу — два символа из алфавита С с минимальными  
 
частотами. Пусть С — алфавит, полученный из алфавита С путем удаления сим-
 
волов х и у и добавления нового символа z, так что С = С — {х,у} U {z}.  
 
По определению частоты / в алфавите С совпадают с частотами в алфавите С,  
 
за исключением частоты / [z] = f [x] + / [у]. Пусть Т" — произвольное дерево,  
 
представляющее оптимальный префиксный код для алфавита С Тогда дерево Г,  
 
полученное из дерева Т" путем замены листа z внутренним узлом с дочерними  
 
элементами х и у, представляет оптимальный префиксный код для алфавита С.  
 
Доказательство. Сначала покажем, что стоимость В (Г) дерева Г можно выра-
 
зить через стоимость В (Т") дерева Т", рассматривая стоимости компонентов из
 
уравнения A6.5). Для каждого символа се С — {х,у} выполняется соотношение
 
466 Часть IV. Усовершенствованные методы разработки и анализа
 
Aт {с) = йт' (с), следовательно, /[с](с) = /[c]d^'(c). Поскольку dr (#) =  
 
= dr (у) = dr' B:) + 1, получаем соотношение
 
/ [х] dT (х) + f [у] dT (у) = (/ [х] + f {у}) {dT> (z) + 1) =
 
из которого следует равенство
 
B(T) = B(T')+f[x)
 
ИЛИ
 
B{T')=B(T)-f[x]-f[y].
 
Докажем лемму методом от противного. Предположим, дерево Т не представ-
 
ляет оптимальный префиксный код для алфавита С Тогда существует дерево Т",  
 
для которого справедливо неравенство В(Т") < В{Т). Согласно лемме 16.2, х
 
и у без потери общности можно считать дочерними элементами одного и того
 
же узла. Пусть дерево Т"' получено из дерева Т" путем замены элементов х и у
 
листом z с частотой / [z] = / [х] + f [у]. Тогда можно записать:
 
B(T"')=B{T")-f[x}-f{y}<B(T)-f[x}-f[y} = B(T'),  
 
что противоречит предположению о том, что дерево Т' представляет оптимальный  
 
префиксный код для алфавита С. Таким образом, дерево Г должно представлять
 
оптимальный префиксный код для алфавита С. ?
 
 
{{Теорема
 
{{Теорема
 
|id=th1
 
|id=th1
 
|statement=
 
|statement=
Процедура Huffman дает оптимальный префиксный код.  
+
Алгоритм Хаффмана дает оптимальный префиксный код.  
 
|proof=
 
|proof=
Справедливость теоремы непосредственно следует из лемм 1 и 2
+
Справедливость теоремы непосредственно следует из лемм (1) и (2)
 
}}
 
}}
 +
 +
== См. также ==
 +
*[[Оптимальное_хранение_словаря_в_алгоритме_Хаффмана | Оптимальное хранение словаря в алгоритме Хаффмана]]
 +
 +
== Источники информации ==
 +
 +
* Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн. Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — с. 459. — ISBN 5-8489-0857-4
 +
*[http://en.wikipedia.org/wiki/Huffman_coding Wikipedia — Huffman coding]
 +
*[http://ru.wikipedia.org/wiki/%C4%E2%EE%E8%F7%ED%EE%E5_%E4%E5%F0%E5%E2%EE Википедия — Бинарное дерево]
 +
*[http://ru.wikipedia.org/wiki/Префиксный_код Википедия — Префиксный код]
 +
 +
[[Категория: Дискретная математика и алгоритмы]]
 +
 +
[[Категория:Алгоритмы сжатия]]

Текущая версия на 19:41, 4 сентября 2022

Алгоритм Хаффмана (англ. Huffman's algorithm) — алгоритм оптимального префиксного кодирования алфавита. Был разработан в 1952 году аспирантом Массачусетского технологического института Дэвидом Хаффманом при написании им курсовой работы. Используется во многих программах сжатия данных, например, PKZIP 2, LZH и др.

Определение

Определение:
Пусть [math]A=\{a_{1},a_{2}, \ldots ,a_{n}\}[/math] — алфавит из [math]n[/math] различных символов, [math]W=\{w_{1},w_{2}, \ldots ,w_{n}\}[/math] — соответствующий ему набор положительных целых весов. Тогда набор бинарных кодов [math]C=\{c_{1},c_{2}, \ldots ,c_{n}\}[/math], где [math]c_{i}[/math] является кодом для символа [math]a_{i}[/math], такой, что:
  • [math]c_{i}[/math] не является префиксом для [math]c_{j}[/math], при [math]i \ne j[/math],
  • cумма [math]\sum\limits_{i \in [1, n]} w_{i}\cdot |c_{i}|[/math] минимальна ([math]|c_{i}|[/math] — длина кода [math]c_{i}[/math]),
называется кодом Хаффмана.


Алгоритм построения бинарного кода Хаффмана

Построение кода Хаффмана сводится к построению соответствующего бинарного дерева по следующему алгоритму:

  1. Составим список кодируемых символов, при этом будем рассматривать один символ как дерево, состоящее из одного элемента c весом, равным частоте появления символа в строке.
  2. Из списка выберем два узла с наименьшим весом.
  3. Сформируем новый узел с весом, равным сумме весов выбранных узлов, и присоединим к нему два выбранных узла в качестве детей.
  4. Добавим к списку только что сформированный узел вместо двух объединенных узлов.
  5. Если в списке больше одного узла, то повторим пункты со второго по пятый.

Время работы

Если сортировать элементы после каждого суммирования или использовать приоритетную очередь, то алгоритм будет работать за время [math]O(N \log N)[/math].Такую асимптотику можно улучшить до [math]O(N)[/math], используя обычные массивы.

Пример

Дерево Хаффмана для слова [math]abracadabra[/math]

Закодируем слово [math]abracadabra[/math]. Тогда алфавит будет [math]A= \{a, b, r, c, d\} [/math], а набор весов (частота появления символов алфавита в кодируемом слове) [math]W=\{5, 2, 2, 1, 1\}[/math]:

В дереве Хаффмана будет [math]5[/math] узлов:

Узел a b r с d
Вес 5 2 2 1 1

По алгоритму возьмем два символа с наименьшей частотой — это [math]c[/math] и [math]d[/math]. Сформируем из них новый узел [math]cd[/math] весом [math]2[/math] и добавим его к списку узлов:

Узел a b r cd
Вес 5 2 2 2

Затем опять объединим в один узел два минимальных по весу узла — [math]r[/math] и [math]cd[/math]:

Узел a rcd b
Вес 5 4 2

Еще раз повторим эту же операцию, но для узлов [math]rcd[/math] и [math]b[/math]:

Узел brcd a
Вес 6 5

На последнем шаге объединим два узла — [math]brcd[/math] и [math]a[/math]:

Узел abrcd
Вес 11

Остался один узел, значит, мы пришли к корню дерева Хаффмана (смотри рисунок). Теперь для каждого символа выберем кодовое слово (бинарная последовательность, обозначающая путь по дереву к этому символу от корня):

Символ a b r с d
Код 0 11 101 1000 1001

Таким образом, закодированное слово [math]abracadabra[/math] будет выглядеть как [math]01110101000010010111010[/math]. Длина закодированного слова — [math]23[/math] бита. Стоит заметить, что если бы мы использовали алгоритм кодирования с одинаковой длиной всех кодовых слов, то закодированное слово заняло бы [math]33[/math] бита, что существенно больше.

Корректность алгоритма Хаффмана

Чтобы доказать корректность алгоритма Хаффмана, покажем, что в задаче о построении оптимального префиксного кода проявляются свойства жадного выбора и оптимальной подструктуры. В сформулированной ниже лемме показано соблюдение свойства жадного выбора.

Лемма (1):
Пусть [math]C[/math] — алфавит, каждый символ [math]c \in C[/math] которого встречается с частотой [math]f[c][/math]. Пусть [math]x[/math] и [math]y[/math] — два символа алфавита [math]C[/math] с самыми низкими частотами. Тогда для алфавита [math]C[/math] существует оптимальный префиксный код, кодовые слова символов [math]x[/math] и [math]y[/math] в котором имеют одинаковую максимальную длину и отличаются лишь последним битом.
Доказательство:
[math]\triangleright[/math]

Возьмем дерево [math]T[/math], представляющее произвольный оптимальный префиксный код для алфавита [math]C[/math]. Преобразуем его в дерево, представляющее другой оптимальный префиксный код, в котором символы [math]x[/math] и [math]y[/math] — листья с общим родительским узлом, находящиеся на максимальной глубине.

Пусть символы [math]a[/math] и [math]b[/math] имеют общий родительский узел и находятся на максимальной глубине дерева [math]T[/math]. Предположим, что [math]f[a] \leqslant f[b][/math] и [math]f[x] \leqslant f[y][/math]. Так как [math]f[x][/math] и [math]f[y][/math] — две наименьшие частоты, а [math]f[a][/math] и [math]f[b][/math] — две произвольные частоты, то выполняются отношения [math]f[x] \leqslant f[a][/math] и [math]f[y] \leqslant f[b][/math]. Пусть дерево [math]T'[/math] — дерево, полученное из [math]T[/math] путем перестановки листьев [math]a[/math] и [math]x[/math], а дерево [math]T''[/math] — дерево полученное из [math]T'[/math] перестановкой листьев [math]b[/math] и [math]y[/math]. Разность стоимостей деревьев [math]T[/math] и [math]T'[/math] равна:

[math]B(T) - B(T') = \sum\limits_{c \in C} f(c)d_T(c) - \sum\limits_{c \in C} f(c)d_{T'}(c) = (f[a] - f[x])(d_T(a) - d_T(x)),[/math]

что больше либо равно [math]0[/math], так как величины [math]f[a] - f[x][/math] и [math]d_T(a) - d_T(x)[/math] неотрицательны. Величина [math]f[a] - f[x][/math] неотрицательна, потому что [math]x[/math] — лист с минимальной частотой, а величина [math]d_T(a) - d_T(x)[/math] является неотрицательной, так как лист [math]a[/math] находится на максимальной глубине в дереве [math]T[/math]. Точно так же перестановка листьев [math]y[/math] и [math]b[/math] не будет приводить к увеличению стоимости. Таким образом, разность [math]B(T') - B(T'')[/math] тоже будет неотрицательной.

Таким образом, выполняется неравенство [math]B(T'') \leqslant B(T)[/math]. С другой стороны, [math]T[/math] — оптимальное дерево, поэтому должно выполняться неравенство [math]B(T) \leqslant B(T'')[/math]. Отсюда следует, что [math]B(T) = B(T'')[/math]. Значит, [math]T''[/math] — дерево, представляющее оптимальный префиксный код, в котором символы [math]x[/math] и [math]y[/math] имеют одинаковую максимальную длину, что и доказывает лемму.
[math]\triangleleft[/math]
Лемма (2):
Пусть дан алфавит [math]C[/math], в котором для каждого символа [math]c \in C[/math] определены частоты [math]f[c][/math]. Пусть [math]x[/math] и [math]y[/math] — два символа из алфавита [math]C[/math] с минимальными частотами. Пусть [math]C'[/math] — алфавит, полученный из алфавита [math]C[/math] путем удаления символов [math]x[/math] и [math]y[/math] и добавления нового символа [math]z[/math], так что [math]C' = C \backslash \{ x, y \} \cup {z}[/math]. По определению частоты [math]f[/math] в алфавите [math]C'[/math] совпадают с частотами в алфавите [math]C[/math], за исключением частоты [math]f[z] = f[x] + f[y][/math]. Пусть [math]T'[/math] — произвольное дерево, представляющее оптимальный префиксный код для алфавита [math]C'[/math] Тогда дерево [math]T[/math], полученное из дерева [math]T'[/math] путем замены листа [math]z[/math] внутренним узлом с дочерними элементами [math]x[/math] и [math]y[/math], представляет оптимальный префиксный код для алфавита [math]C[/math].
Доказательство:
[math]\triangleright[/math]

Сначала покажем, что стоимость [math]B(T)[/math] дерева [math]T[/math] может быть выражена через стоимость [math]B(T')[/math] дерева [math]T'[/math]. Для каждого символа [math]c \in C \backslash \{x, y \}[/math] верно [math]d_T(C) = d_{T'}[/math], значит, [math]f[c]d_T(c) = f[c]d_{T'}(c)[/math]. Так как [math]d_T(x) = d_T(y) = d_{T'} (z) + 1[/math], то

[math]f[x]d_T(x) + f[y]d_T(y) = (f[x] + f[y])(d_{T'}(z) + 1) = f[z]d_{T'}(z) + (f[x] + f[y])[/math]

из чего следует, что

[math] B(T) = B(T') + f[x] + f[y] [/math]

или

[math] B(T') = B(T) - f[x] - f[y] [/math]

Докажем лемму от противного. Предположим, что дерево [math]T[/math] не представляет оптимальный префиксный код для алфавита [math]C[/math]. Тогда существует дерево [math]T''[/math] такое, что [math]B(T'') \lt B(T)[/math]. Согласно лемме (1), элементы [math]x[/math] и [math]y[/math] можно считать дочерними элементами одного узла. Пусть дерево [math]T'''[/math] получено из дерева [math]T''[/math] заменой элементов [math]x[/math] и [math]y[/math] листом [math]z[/math] с частотой [math]f[z] = f[x] + f[y][/math]. Тогда

[math]B(T''') = B(T'') - f[x] - f[y] \lt B(T) - f[x] - f[y] = B(T')[/math],

что противоречит предположению о том, что дерево [math]T'[/math] представляет оптимальный префиксный код для алфавита [math]C'[/math]. Значит, наше предположение о том, что дерево [math]T[/math] не представляет оптимальный префиксный код для алфавита [math]C[/math], неверно, что и доказывает лемму.
[math]\triangleleft[/math]
Теорема:
Алгоритм Хаффмана дает оптимальный префиксный код.
Доказательство:
[math]\triangleright[/math]
Справедливость теоремы непосредственно следует из лемм (1) и (2)
[math]\triangleleft[/math]

См. также

Источники информации