Пусть сообщение M составлено из символов алфавита C. Эти символы могут быть представлены кодами некоторой фиксированной длины (для определенности 8 бит). Допустим, числа повторений символов A, B, C и D (Na, Nb, Nc и Nd соответственно) удовлетворяют неравенству
Число повторений символа B произвольно. Назначим этим символам следующие коды:
A: | 00000000 |
B: | 00000001 |
C: | 00000010 |
D: | 00000011 |
Оставшиеся 252 кода произвольно распределим между оставшимися символами алфавита. Далее сделаем следующую замену кодов:
A: | 0000000 |
B: | 00000010 |
C: | 000000110 |
D: | 000000111 |
В результате получим экономию в Na-(Nc+Nd) бит (на самом деле меньше, т.к. вместе с сообщением надо передавать таблицу кодов). Также важно отметить, что проблем с декодированием не возникает - ни один из кодов не является префиксом другого. Декодер довольно прост, о нем чуть ниже.
Алгоритм Хаффмана основан на этой идее с той лишь разницей, что она прменяется ко всем символам алфавита. На первом шаге определяются числа повторений (веса) всех символов. На втором шаге строится дерево кодирования. Используются две структуры данных - массив и дерево, в массив заносятся все символы и их веса, дерево пусто. Затем ищутся два самых редких символа, исключаются из массива и добавляются в дерево (листья). Также создается новый узел дерева с весом равным сумме весов найденных символов и содержащий ссылки на эти символы. Этот узел добавляется в массив. Процедура повторяется пока в массиве не останется один элемент. Третий шаг - собственно кодирование.
В качестве примера рассмотрим сообщение, составленное из шести символов A, B, C, D, E и F. Числа повторений следующие:
A | B | C | D | E | F |
60 | 25 | 30 | 5 | 10 | 20 |
Возьмем два узла с самыми маленькими весами (D и E), образуем из них новый узел с весом 15 и добавим его в список. Узлы D и E исключим:
A | B | C | F | |||
60 | 25 | 30 | 20 | 15 | ||
/ | \ | |||||
E | D | |||||
10 | 5 |
Возьмем два узла с наименьшими весами (только что созданный и F), образуем из них новый узел с весом 35 и добавим его в список. Взятые узлы исключим:
A | B | C | ||||
60 | 25 | 30 | 35 | |||
/ | \ | |||||
| | 15 | |||||
| | / | \ | ||||
F | E | D | ||||
20 | 10 | 5 |
Возьмем два узла с наименьшими весами (B и C), образуем из них новый узел с весом 55 и добавим его в список. Взятые узлы исключим:
A | |||||||
60 | 35 | 55 | |||||
/ | \ | / | \ | ||||
| | 15 | | | | | ||||
| | / | \ | | | | | |||
F | E | D | C | B | |||
20 | 10 | 5 | 30 | 25 |
Возьмем два узла с наименьшими весами (35 и 55), образуем из них новый узел с весом 90 и добавим его в список. Взятые узлы исключим:
A | ||||||||
60 | – | 90 | – | |||||
/ | \ | |||||||
55 | 35 | |||||||
/ | \ | / | \ | |||||
| | | | | | 15 | |||||
| | | | | | / | \ | ||||
C | B | F | E | D | ||||
30 | 25 | 20 | 10 | 5 |
Возьмем два узла с наименьшими весами (A и 90) и образуем из них новый узел с весом 150 и добавим его в список. Взятые узлы исключим. В списке остался один узел - дерево построено:
– | – | 150 | – | – | |||||
/ | \ | ||||||||
– | 90 | – | | | ||||||
/ | \ | | | |||||||
55 | 35 | | | |||||||
/ | \ | / | \ | | | |||||
| | | | | | 15 | | | |||||
| | | | | | / | \ | | | ||||
C | B | F | E | D | A | ||||
30 | 25 | 20 | 10 | 5 | 60 |
Чтобы получить код Хаффмана для любого из символов нужно пройти от корня дерева к соответствующему этому символу листу, каждый переход влево - 0, вправо - 1. Так получим все биты кода. Реально приходится двигаться в обратном направлении - от листа к корню. Процедура получения кода похожа на преобразование числа в строку - последний символ строки получается первым. Следует заметить, что длина кода может превосходить длину простой переменной (16 или 32 бит). В данном примере коды следующие:
A: | 1 |
B: | 001 |
C: | 000 |
D: | 0111 |
E: | 0110 |
F: | 010 |
Декодирование символа начинается с корня дерева. Сообщение читается по одному биту, если прочитан 0 - переход по дереву влево, если 1 - вправо. По достижению листа определяется конец кода символа (и сам этот символ).
Алгоритм Хаффмана дает самый короткий код среди всех кодов, представляющих каждый символ сообщения фиксированной последовательностью бит. Доказательства этого факта строится на следующем свойстве оптимального кода - два наиболее редких символа A и B представляются кодами максимальной длины, т.е. коды всех прочих символов заведомо не длиннее кода A (B). Без ограничения общности Na - число повторений символа A не больше Nb - числа поторений символа B. Допустим, что код символа С встречающегося Nc>Na раз имеет длину Lc>La. Тогда после обмена кодов A и C получим более короткий код:
Это противоречит оптимальности кода и, следовательно, Lc не может превосходить La. Предположение Lb меньше La также неверно - в этом случае либо есть символ D встречающийся Nd>Nb раз, длина кода которого равна La, либо код символа A содержит лишние биты. Сделав одну замену можно получить оптимальный код код, в котором A и B различаются лишь последним битом. Таким образом, существует оптимальное дерево кодирования, в котором коды двух самых редких символов отличаются лишь последним битом.
Обозначим C(N) алфавит из N символов, M(C) - сообщение, образованное из символов этого алфавита, T(M) -дерево кодирования и W(T) - вес дерева T, т.е. сумму по всем символам произведений чисел повторений символа на длину кода этого символа.
Допустим, что дерево Хаффмана T(M) неоптимально, т.е. существует некоторое дерево T'(M), вес которого меньше веса дерева Хаффмана:
Пусть A и В - два наиболее редких символа сообщения M, их числа повторений Na и Nb соответственно. И в дереве T и в дереве T' их коды различаются лишь младшим битом (в дереве Хаффмана - по построению, дерево T' может быть преобразовано к такому виду). Заменим все символы A и B новым (отсутстующим в алфавите C) символом Z. Число повторений этого символа в сообщении Nz=Na+Nb. В результате получим новый алфавит C'(N-1) и новое сообщение M'(C'). Из деревьев T и T' исключим листья, соответствующие символам A и В, а их общий узел поставим в соответствие символу Z. Получим деревья T''(M') и T'''(M'), T'' - дерево Хаффмана для сообщения M'. Поскольку такие преобразования уменьшают веса обоих деревьев на одинаковую величину Nz (код символа Z на один бит короче кода символа A) верно неравенство:
Это означает, что дерево Хаффмана для сообщения из алфавита C'(N-1) также неоптимально. Продолжая процесс слияния самых редких символов придем к тому, что дерево Хаффмана для сообщения из алфавита C(2) неоптимально. Но это не так - каждый символ сообщения представляется одним битом. Противоречие доказывает оптимальность кода Хаффмана. Но это совсем не означает, что алгоритм Хаффмана - лучший алгоритм сжатия.
Ниже приведен текст программы. Как и в программе LZSS здесь есть функции побитового чтения/записи, но здесь они проще, т.к. читать и писать нужно только по одному биту. Информация о кодах символов в архиве представлена в виде таблицы чисел повторений - это не самый экономный способ, но зато самый простой. Содержательная часть начинается с первого define - я понимаю, что возможность вынести общие функции (ввод/вывод и т.п.) в отдельные файлы была бы полезна, но пока ее нет.