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

Материал из Викиконспекты
Перейти к: навигация, поиск
м (rollbackEdits.php mass rollback)
 
(не показано 48 промежуточных версий 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> узлов:
  
== Построение кода Хаффмана ==
 
 
В основу алгоритма Хаффмана положена идея: кодировать более коротко те символы, которые встречаются чаще, а те, которые встречаются реже кодировать длиннее. Для построения кода Хаффмана нам необходима таблица частот символов. Рассмотрим пример построения кода на простой строке '''''abacaba'''''<br>
 
 
{| class="wikitable"
 
{| class="wikitable"
! a || b || c ||
+
! Узел ||  a || b ||  r  || с  || d
 
|-
 
|-
| 4 || 2 || 1 ||
+
| Вес || 5 || 2 || 2 || 1 || 1
 
|}
 
|}
Следующим шагом будет построение дерева, где вершины - "символы", а пути до них соответствуют их префиксным кодам. 
 
Для этого на каждом шаге будем брать два символа с минимальной частотой вхождения, и объединять их в новые так называемые "символы" с частотой равной сумме частот тех символов, которые мы объединяли.
 
В примере мы объединим символы b и с в символ bc с частотой 3.
 
[[Файл:Haffman1.jpg]]
 
  
Хаффман изобрел жадный алгоритм, позволяющий составить оптимальный префиксный код, который получил название код Хаффмана. Доказательство корректности этого алгоритма основывается на свойстве жадного выбора и оптимальной подструктуре. Вместо того чтобы демонстрировать, что эти свойства выполняются, а затем разрабатывать псевдокод, сначала мы представим псевдокод. Это поможет прояснить, как алгоритм осуществляет жадный выбор. В приведенном ниже псевдокоде предполагается, что <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>. В результате слияния двух объектов образуется новый объект, частота появления которого является суммой частот объединенных объектов:
+
По алгоритму возьмем два символа с наименьшей частотой {{---}} это <tex>c</tex> и <tex>d</tex>. Сформируем из них новый узел <tex>cd</tex> весом <tex>2</tex> и добавим его к списку узлов:
<br><br>
+
 
'''Huffman(<tex>C</tex>)''' <br>
+
{| class="wikitable"
<tex>n \gets |C|</tex>  <br>
+
! Узел || a || b || r || cd
<tex>Q \gets C</tex> <br>
+
|-
'''for''' <tex>i \gets 1</tex> '''to''' <tex>n - 1</tex> <br>
+
| Вес || 5 || 2 || 2 || 2
:'''do''' Выделить память для узла <tex>z</tex> <br>
+
|}
::left[<tex> z</tex>]<tex> \gets x \gets</tex> Extract_Min(<tex> Q</tex>)<br>
+
 
::right[<tex>z</tex>]<tex>\gets y \gets </tex> Extract_Min(<tex>Q</tex>) <br>
+
Затем опять объединим в один узел два минимальных по весу узла {{---}} <tex>r</tex> и <tex>cd</tex>:
::<tex>f[z] \gets f[x]+f[y]</tex>
+
 
::Insert(<tex>Q</tex>, <tex>z</tex> ) <br>
+
{| class="wikitable"
'''return''' Extract_Min(<tex>Q</tex> ) <tex> \rhd </tex>  Возврат корня дерева <br><br>
+
! Узел || a || rcd || b
=== Пример работы алгоритма ===
+
|-
[[Файл:Huffman.jpg]]<br>
+
| Вес || 5 || 4 || 2
На каждом этапе показано содержимое очереди, элементы которой рассортированы в порядке возрастания их частот. На каждом шаге работы алгоритма объединяются два объекта (дерева) с самыми низкими частотами. Листья изображены в виде прямоугольников, в каждом из которых указана буква и соответствующая ей частота. Внутренние узлы представлены кругами, содержащими сумму частот дочерних узлов. Ребро, соединяющее внутренний узел с левым дочерним узлом, имеет метку 0, а ребро, соединяющее его с правым дочерним узлом, — метку 1. Слово кода для буквы образуется последовательностью меток на ребрах, соединяющих корень с листом, представляющим эту букву. По скольку данное множество содержит шесть букв, размер исходной очереди равен 6(часть ''а'' рисунка), а для построения дерева требуется пять слияний. Промежуточные этапы изображены в частях ''б-д''. Конечное дерево (''е'') представляет оптимальный префиксный код. Как уже говорилось, слово кода для буквы — это последовательность меток на пути от корня к листу с этой буквой.<br>
+
|}
В строке 2 инициализируется очередь с приоритетами <tex>Q</tex>, состоящая из элементов множества <tex>С</tex>. Цикл '''for''' в строках 3-8 поочередно извлекает по два узла, <tex>x</tex> и <tex>у</tex>, которые характеризуются в очереди наименьшими частотами, и заменяет их в очереди новым узлом, представляющим объединение упомянутых выше элементов. Частота появления <tex>z</tex> вычисляется в строке 7 как сумма частот <tex>x</tex> и <tex>y</tex>. Узел <tex>x</tex> является левым дочерним узлом <tex>z</tex>, а <tex>y</tex> — его правым дочерним узлом. (Этот порядок является произвольным; перестановка левого и правого дочерних узлов приводит к созданию другого кода с той же стоимостью.) После <tex>n - 1</tex> объединений в очереди остается один узел — корень дерева кодов, который возвращается в строке 9.
+
 
=== Оценка времени работы ===
+
Еще раз повторим эту же операцию, но для узлов <tex>rcd</tex> и <tex>b</tex>:
При анализе времени работы алгоритма Хаффмана предполагается, что <tex>Q</tex> реализована как бинарная неубывающая пирамида. Для множества <tex>C</tex>, состоящего из <tex>n</tex> символов, инициализацию очереди <tex>Q</tex> в строке 2 можно выполнить за время <tex>O(n)</tex>. Цикл for в строках 3-8 выполняется ровно <tex>n - 1</tex> раз, и поскольку для каждой операции над пирамидой требуется время<tex>O(lg(n))</tex>, вклад цикла во время работы алгоритма равен <tex>O(n \cdot lg(n))</tex>. Таким образом, полное время работы процедуры Huffman с входным множеством, состоящим из <tex>n</tex> символов, равно <tex>O(n \cdot lg(n))</tex>.  
+
 
 +
{| class="wikitable"
 +
! Узел || 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
 
|id=lemma1
 
|about=1
 
|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> в котором имеют одинаковую длину и отличаются лишь последним битом.  
+
|statement=
|proof=Идея доказательства состоит в том, чтобы взять дерево <tex>T</tex>, представляющее произвольный оптимальный префиксный код, и преобразовать его в дерево, представляющее другой оптимальный префиксный код, в котором символы <tex>x</tex> и <tex>y</tex> являются листьями с общим родительским узлом, причем в новом дереве эти листья находятся на максимальной глубине. Пусть <tex>a</tex> и <tex>b</tex> — два символа, представленные листьями с общим родительским узлом, которые находятся на максимальной глубине дерева <tex>T</tex>. Предположим без потери общности, что <tex>f[a] \le f[b]</tex> и <tex>f[x] \le f[y]</tex>. Поскольку <tex>f[x]</tex> и <tex>f[y]</tex> — две самые маленькие частоты (в указанном порядке), <tex>f[a]</tex> и <tex>f[b]</tex> — две произвольные частоты, то выполняются соотношения <tex>f[x] \le f[a]</tex> и <tex>f[y] \le f[b]</tex>. В результате перестановки в дереве <tex>T</tex> листьев <tex>a</tex> и <tex>x</tex> получается дерево <tex>T'</tex>, а при последующей перестановке в дереве V листьев <tex>b</tex> и <tex>y</tex> получается дерево <tex>T''</tex>. Разность стоимостей деревьев Т и Т" равна <br>
+
Пусть <tex>C</tex> — алфавит, каждый символ <tex>c \in C</tex> которого встречается с частотой <tex>f[c]</tex>. Пусть <tex>x</tex> и <tex>y</tex> — два символа алфавита <tex>C</tex> с самыми низкими частотами.
<tex>B(T) - B(T') = \sum_{c \in C} f(c)d_T(C) - \sum_{c \in C} f(c)d_{T'}(C) =</tex><br><tex> = (f[a] - f[x])(d_T(a) - d_T(x)) \ge 0</tex>,<br>
+
 
поскольку величины <tex>f[a] - f[x]</tex> и <tex>d_T(a) - d_T(x)</tex> неотрицательны. Величина <tex>f[a] - f[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> неотрицательна. Таким образом, выполняется неравенство <tex>B(T') \le B(T'')</tex>, и поскольку <tex>T</tex> — оптимальное дерево, то должно также выполняться неравенство <tex>B(T'') \le B(T')</tex>, откуда следует, что <tex>B(T') = B(T'')</tex>. Таким образом, <tex>T''</tex> — оптимальное дерево, в котором <tex>x</tex> и <tex>y</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> равна:
 +
 
 +
<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> тоже будет неотрицательной.
 +
 
 +
Таким образом, выполняется неравенство <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> имеют одинаковую максимальную длину, что и доказывает лемму.
 
}}
 
}}
+
 
 
{{Лемма
 
{{Лемма
|id=lemma2.
+
|id=lemma2
 
|about=2
 
|about=2
|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 {х,у} \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>.  
+
|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>.  
|proof=Сначала покажем, что стоимость <tex>B(T)</tex> дерева <tex>T</tex> можно выразить через стоимость <tex>B(T')</tex> дерева <tex>T'</tex>. Для каждого символа <tex>c \le C - {x,y}</tex> выполняется соотношение <tex>d_T(C) = d_{T'}(c)</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>, получаем соотношение<br>
+
|proof=
<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>
+
Сначала покажем, что стоимость <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>, то
<br>
+
 
из которого следует равенство <br>
+
<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>
<tex> B(T) = B(T') + f[x] + f[y] </tex> <br>
+
 
ИЛИ <br>
+
из чего следует, что
<tex> B(T') = B(T) - f[x] - f[y] </tex>. <br>
+
 
Докажем лемму методом от противного. Предположим, дерево <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>. Тогда можно записать:<br>
+
<tex> B(T) = B(T') + f[x] + f[y] </tex>
<tex>B(T''') = B(T'') - f[x] - f[y] < B(T) - f[x] -f[y] = B(T')</tex>,<br>
+
 
что противоречит предположению о том, что дерево <tex>T'</tex> представляет оптимальный префиксный код для алфавита <tex>C'</tex>. Таким образом, дерево <tex>T</tex> должно представлять оптимальный префиксный код для алфавита <tex>C</tex>.
+
или
 +
 
 +
<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>. Тогда
 +
 
 +
<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>, неверно, что и доказывает лемму.
 
}}
 
}}
  
Лемма 16.3.
 
Доказательство.
 
 
{{Теорема
 
{{Теорема
 
|id=th1
 
|id=th1
 
|statement=
 
|statement=
Процедура Huffman дает оптимальный префиксный код.  
+
Алгоритм Хаффмана дает оптимальный префиксный код.  
 
|proof=
 
|proof=
 
Справедливость теоремы непосредственно следует из лемм (1) и (2)
 
Справедливость теоремы непосредственно следует из лемм (1) и (2)
 
}}
 
}}
  
== Литература ==
+
== См. также ==
* Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — С. 1296. — ISBN 5-8489-0857-4
+
*[[Оптимальное_хранение_словаря_в_алгоритме_Хаффмана | Оптимальное хранение словаря в алгоритме Хаффмана]]
 +
 
 +
== Источники информации ==
 +
 
 +
* Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн. Алгоритмы: построение и анализ — 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 и др.

Определение

Определение:
Пусть A={a1,a2,,an} — алфавит из n различных символов, W={w1,w2,,wn} — соответствующий ему набор положительных целых весов. Тогда набор бинарных кодов C={c1,c2,,cn}, где ci является кодом для символа ai, такой, что:
  • ci не является префиксом для cj, при ij,
  • cумма i[1,n]wi|ci| минимальна (|ci| — длина кода ci),
называется кодом Хаффмана.


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

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

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

Время работы

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

Пример

Дерево Хаффмана для слова abracadabra

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

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

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

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

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

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

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

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

Узел brcd a
Вес 6 5

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

Узел abrcd
Вес 11

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

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

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

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

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

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

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

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

B(T)B(T)=cCf(c)dT(c)cCf(c)dT(c)=(f[a]f[x])(dT(a)dT(x)),

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

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

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

f[x]dT(x)+f[y]dT(y)=(f[x]+f[y])(dT(z)+1)=f[z]dT(z)+(f[x]+f[y])

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

B(T)=B(T)+f[x]+f[y]

или

B(T)=B(T)f[x]f[y]

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

B(T)=B(T)f[x]f[y]<B(T)f[x]f[y]=B(T),

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

См. также

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