Рассмотренный выше алгоритм Хаффмана можно изменить так, чтобы он выполнялся за один проход и не требовал информации о структуре дерева кодирования:
Декодирование возможно, поскольку обе функции обновляют дерево кодирования одинаковым образом. Псевдокод не вполне точен - в нем не указано условие прекращения декодирования и некоторые другие детали.
Инициализировать дерево кодирования можно исходя из некоторого предположения о частотах повторений различных символов. Можно добавить к 256 символам еще два - EOF (код 256) и CHR (код 257) и включить в дерево только их. Другие символы будут включены в дерево после их первого появления в кодируемом файле. Соответственно, при первом появлении символа в выходной файл записывается код CHR и сам символ (восемь бит), при последующих появлениях этого символа в выходной файл записывается его код (символ уже включен в дерево и может быть закодирован). Выходной файл завершается кодом EOF. При декодировании появлене кода EOF является условием завершения. Получаемый таким образом код не будет кодом Хаффмана, поскольку коды символов могут меняться по ходу кодирования.
В принципе можно после считывания каждого символа строить дерево Хаффмана заново, это потребует весьма значительных затрат времени, но работать будет. Для начала так и сделаем, в программе статического кодирования заменим функции Init, Encode и Decode, добавим Insert и Update. Теперь построение дерева рассредоточено по трем функциям - Init (инициализация дерева), Insert (регистрация нового символа) и Update (перестроение дерева). Далее функция Update будет заменена гораздо более быстродействующей.
Не пытайтесь кодировать с помощью этой программы большие файлы и не думайте что она зависла - просто она медленно работает.
Более внимательное рассмотрение свойств опимального дерева кодирования приводит к существенно лучшим алгоритмам. В дереве Хаффмана более часто встречающимся символам соответствуют более короткие коды. Для любых двух узлов A и B верно следующее:
Na и Nb - веса узлов, La и Lb - длины кодов. Из предположения обратного следует уменьшение веса дерева при перестановке A и B, что противоречит оптимальности:
Таким образом, соотношение между весами и длинами кодав является необходимым условием оптимальности. Но также оно является достаточным условием, т.е. из его истинности следует оптимальность дерева. Доказательство аналогично доказательству оптимальности дерева Хаффмана. Пусть A - самый редкий символ сообщения. Из соотношения весов и длин кодов следует, что либо длина La его кода максимальна, либо существует символ C с весом Nc=Na и длиной Lc>La. В последнем случае переставим узлы A и С. Действительно, для любого символа C c весом Nc>Na длина кода Lc<=La (соотношение весов и длин кодов). Символ B - следующий после A по весу, Nb>=Na. Если допустить, что Lb<La, то существует символ D c весом Nd>=Nb и длиной Ld=La>Lb. Если Nd>Nb, приходим к противоречию, остается вариант Nd=Nb и после перестановки B и D придем к Lb=La и кодам A и B отличающимся только последним битом.
Предположим, дерево T, для которого выполнено соотношение весов и длин кодов неоптимально, т.е. существует дерево T' с весом W(T')<W(T).
В обоих деревьях коды символов A и B различаются последним битом. Заменим алфавит A, B -> Z, Nz=Na+Nb. Веса обоих деревьев кодирования уменьшатся на одинаковую величину Na+Nb бит, соотношение весов и длин кодов не нарушится. Продолжая процесс слияния самых редких символов придем к тому, что дерево кодирования для алфавита из двух символов неоптимально. Противоречие доказывает оптимальность дерева T.
Пусть имеется уже построенное оптимальное дерево и вес одного из символов (A) нужно увеличить на единицу. Простое увеличение веса соответствующего символу узла и всех лежащих выше узлов может нарушить оптимальность. Пусть Na - вес символа (до увеличения), La - длина кода. Nb и Lb - вес и длина некоторого другого узла (не обязательно символа). Перестановка узлов A и B приведет к следующему измененению веса дерева:
вес дерева уменьшится лишь при равенстве весов A и B и при меньшей длине кода B. Этой перестановки недостаточно для восстановления оптимальноти дерева, но она дает ключ к построению алгоритма.
Заметим, что перестановка двух равных по весу поддеревьев не меняет веса дерева и, следовательно, не нарушает указанного выше соотношения весов и длин кодов (обратное противоречит оптимальности дерева). Сделаем следующее:
Все эти перестановки не нарушают оптимальности дерева. Затем увеличим на единицу веса всех узлов на пути от корня до A. Такое увеличение не нарушит соотношение между весами узлов и длинами кодов, поскольку после сделанных перестановок всем узлам на этом пути соответствуют коды минимальной возможной для их весов длины. Следовательно, полученное дерево будет оптимальным.
Для начала реализуем все это в виде двух рекурсивных процедур Locate и Update. Первая из них обходит дерево и находит узел с заданным весом и самым коротким кодом, вторая реализует привденный алгоритм. Процедура Insert стала более сложной - если старая процедура только регистрировала новый символ, то новая вставляет в дерево узел с нулевым весом (поскольку длина его кода максимальна, это не нарушает соотношение весов и длин кодов):
Программа стала работать быстрее, но все равно не быстро. Поскольку выполнено соотношение весов и длин кодов, узлы дерева кодирования могут быть одновременно упорядочены по уменьшению веса и увеличению длины кода. Действительно, ничто не мешает упорядочить узлы с одинаковыми весами по увеличению длины кода, коды узлов с меньшими весами как минимум не короче кодов узлов с большими весами. Используя упорядоченный массив поиск узла с заданным весом и самым коротким кодом можно выполнять значительно быстрее:
И наконец, рекурсивную процедуру обновления можно заменить циклической:
Такой способ обновления дерева кодирования был предложен Робертом Галлагером в 1978 году. Нужно отметить, что все три способа перестроения дерева приводят к немного разным результатам. Это нормально, т.к. узел с заданным весом и самым коротким кодом может быть не единственным. Общее лишь то, что на каждом шаге кодирования получается оптимальное дерево, соответствующее накопленной статистике.
Теперь о длине кода. Можно создать такое сообщение, каждый байт которого будет закодирован не менее чем девятью битами. Поскольку всего кодов 258, в дереве всегда есть коды длиной более 8 бит. Следующая процедура создает сообщение, начинющееся с 256 различных символов, за которыми следую следуют символы с максимально длинными кодами:
Таким образом, адаптивное кодирование может быть хуже статического - в худшем случае статический код длиннее исходного сообщения на длину таблицы частот, адаптивный код - как минимум, на 12.5%.