Опубликован: 10.10.2007 | Уровень: профессионал | Доступ: платный | ВУЗ: Московский государственный университет имени М.В.Ломоносова
Лекция 1:

Методы сжатия без потерь

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

Один из классических алгоритмов, известных с 60-х годов. Использует только частоту появления одинаковых байт во входном блоке данных. Сопоставляет символам входного потока, которые встречаются чаще, цепочку битов меньшей длины. И, напротив, встречающимся редко - цепочку большей длины. Для сбора статистики требует двух проходов по входному блоку (также существуют однопроходные адаптивные варианты алгоритма).

Для начала введем несколько определений.

Определение. Пусть задан алфавит \Psi  = \{ a_1 ,...,a_r \}, состоящий из конечного числа букв. Конечную последовательность символов из \psi

A = a_{i_1 } a_{i_2 } ...a_{i_n }

будем называть словом в алфавите \psi, а число n - длиной слова A. Длина слова обозначается как l(A)

Пусть задан алфавит, \Omega  = \{ b_1 ,...,b_q \} . Через B обозначим слово в алфавите \Omega и через S(\Omega ) - множество всех непустых слов в алфавите \Omega .

Пусть S = S(\Psi ) - множество всех непустых слов в алфавите \psi , и $S'$ - некоторое подмножество множества S. Пусть также задано отображение F, которое каждому слову A, A \in S(\Psi ), ставит в соответствие слово

B = S(A), B \in S(\Omega ).

Слово В будем назвать кодом сообщения A, а переход от слова A к его коду - кодированием.

Определение. Рассмотрим соответствие между буквами алфавита \psi и некоторыми словами алфавита \Omega :

a_1  - B_1

a_2  - B_2

...

a_r  - B_r

Это соответствие называют схемой и обозначают через \sum {} . Оно определяет кодирование следующим образом: каждому слову A = a_{i_1 } a_{i_2 } ...a_{i_n } из {\rm{ S'(}}\Omega {\rm{) = S(}}\Omega {\rm{)}} ставится в соответствие слово B = B_{i_1 } B_{i_2 } ...B_{i_n } , называемое кодом слова A. Слова B_1 ...B_r называются элементарными кодами. Данный вид кодирования называют алфавитным кодированием.

Определение. Пусть слово В имеет вид

B = B' \cdot B''

Тогда слово B' называется началом или префиксом слова B, а B'' - концом слова B. При этом пустое слово \Lambda и само слово B считаются началами и концами слова B.

Определение. Схема \sum {} обладает свойством префикса, если для любых i и j ( 1 \le i,{\rm{ }}j \le r,{\rm{ }}i \ne j ) слово B_i не является префиксом слова B_j .

Теорема 1. Если схема \sum {} обладает свойством префикса, то алфавитное кодирование будет взаимно однозначным.

Доказательство теоремы можно найти в [1.4].

Предположим, что задан алфавит \psi  = \{ a_1 ,...a_r \} ,{\rm{ }}r > 1 и набор вероятностей p_1 ,...,p_r {\rm{ }}(\sum\limits_{i = 1}^r {p_i  = 1} ) появления символов {\rm{ }}a_1 ,...,a_r. Пусть, далее, задан алфавит {\rm{ }}\Omega , {\rm{ }}\Omega  = \{ b_1 ,...,b_q \} ,{\rm{ (}}q > 1). Тогда можно построить целый ряд схем \sum {} алфавитного кодирования

a_1  - B_1

a_2  - B_2

...

a_r  - B_r

обладающих свойством взаимной однозначности.

Для каждой схемы можно ввести среднюю длину l_{cp} , определяемую как математическое ожидание длины элементарного кода:

l_{cp}  = \sum\limits_{i = 1}^r {l_i p_i } ,{\rm{   }}l_i  = l(B_i ) - длины слов.

Длина l_{cp} показывает, во сколько раз увеличивается средняя длина слова при кодировании с помощью схемы \sum {} .

Можно показать, что l_{cp} достигает величины своего минимума l_* на некоторой \sum {} и определяется как

l_*  = \mathop {\min }\limits_\Sigma  l_{cp}^\Sigma

Определение. Коды, определяемые схемой \sum {} с l_{cp}  = l_*, называются кодами с минимальной избыточностью, или кодами Хаффмана.

Коды с минимальной избыточностью дают в среднем минимальное увеличение длин слов при соответствующем кодировании.

В нашем случае, алфавит \Psi  = \{ a_1 ,...,a_r \} задает символы входного потока, а алфавит \Omega  = \{ 0,1\} т.е. состоит всего из нуля и единицы.

Алгоритм построения схемы \sum {} можно представить следующим образом:

Шаг 1. Упорядочиваем все буквы входного алфавита в порядке убывания вероятности. Считаем все соответствующие слова B_i из алфавита \Omega  = \{ 0,1\} пустыми.

Шаг 2. Объединяем два символа a_{i_{r - 1} } 
и a_{i_r } с наименьшими вероятностями p_{i_{r - 1} } и p_{i_r } в псевдосимвол a'{\rm{ }}\{ a_{i_{r - 1} } a_{i_r } \} c вероятностью p_{i_{r - 1} }  + p_{i_r } . Дописываем 0 в начало слова B_{i_{r - 1} } (B_{i_{r - 1} }  = 0B_{i_{r - 1} } ), и 1 в начало слова B_{i_r } (B_{i_r }  = 1B_{i_r } ).

Шаг 3. Удаляем из списка упорядоченных символов a_{i_{r - 1} } и a_{i_r } , заносим туда псевдосимвол a'{\rm{ }}\{ a_{i_{r - 1} } a_{i_r } \} . Проводим шаг 2, добавляя при необходимости 1 или ноль для всех слов B_i , соответствующих псевдосимволам, до тех пор, пока в списке не останется 1 псевдосимвол.

Пример: Пусть у нас есть 4 буквы в алфавите \Psi  = \{ a_1 ,...,a_4 \} {\rm{ }}(r = 4), p_1  = 0.5,{\rm{ }}p_2  = 0.24,{\rm{ }}p & _3  = 0.15,{\rm{ }}p_4  = 0.11{\rm{ }}\left( {\sum\limits_{i = 1}^4 {{\rm{p}}_i  = 1} } \right). Процесс построения схемы представлен на рис 1.1 :


Рис. 1.1.

Производя действия, соответствующие 2-му шагу, мы получаем псевдосимвол с вероятностью 0.26 (и приписываем 0 и 1 соответствующим словам). Повторяя же эти действия для измененного списка, мы получаем псевдосимвол с вероятностью 0.5. И, наконец, на последнем этапе мы получаем суммарную вероятность 1.

Для того, чтобы восстановить кодирующие слова, нам надо пройти по стрелкам от начальных символов к концу получившегося бинарного дерева. Так, для символа с вероятностью p_4 получим B_4  = 101, для p_3 получим B_3  = 100, для p_2 получим B_2  = 11, для p_1 получим B_1  = 0. Что соответствует схеме:

\eqalign{
  & a_1  - 0  \cr 
  & a_2  - 11  \cr 
  & a_3  - 100  \cr 
  & a_4  - 101 \cr}

Эта схема представляет собой префиксный код, являющийся кодом Хаффмана. Самый часто встречающийся в потоке символ a_1 мы будем кодировать самым коротким словом 0, а самый редко встречающийся a_4 - длинным словом 101.

Для последовательности из 100 символов, в которой символ a_1 встретится 50 раз, символ a_2 - 24 раза, символ a_3 - 15 раз, а символ a_4 - 11 раз, данный код позволит получить последовательность из 176 битов (100 \cdot l_{cp}  = \sum\limits_{i = 1}^4 {p_i l_i } ). Т.е. в среднем мы потратим 1.76 бита на символ потока.

Доказательства теоремы, а также того, что построенная схема действительно задает код Хаффмана, заинтересованный читатель найдет в [1.4].

Как стало понятно из изложенного выше, канонический алгоритм Хаффмана требует помещения в файл со сжатыми данными таблицы соответствия кодируемых символов и кодирующих цепочек.

На практике используются его разновидности. Так, в некоторых случаях резонно либо использовать постоянную таблицу, либо строить ее адаптивно, т.е. в процессе архивации/разархивации. Эти приемы избавляют нас от двух проходов по входному блоку и необходимости хранения таблицы вместе с файлом. Кодирование с фиксированной таблицей применяется в качестве последнего этапа архивации в JPEG и в алгоритме CCITT Group, рассмотренных в разделе "Алгоритмы сжатия изображений".

Характеристики канонического алгоритма Хаффмана:

Степени сжатия: 8, 1.5, 1 (лучшая, средняя, худшая степени).

Симметричность по времени: 2:1 (за счет того, что требует двух проходов по массиву сжимаемых данных).

Характерные особенности: Один из немногих алгоритмов, который не увеличивает размера исходных данных в худшем случае (если не считать необходимости хранить таблицу перекодировки вместе с файлом).

Сергей Иванов
Сергей Иванов
Россия, г. Смоленск ул. Николаева д. 19а кв. 56
Ирина Усанова
Ирина Усанова
Россия, г. Чистополь