Алгоритм LZW
Непосредственным предшественником LZW является алгоритм LZ78, опубликованный Абрахамом Лемпелем (Abraham Lempel) и Якобом Зивом (Jacob Ziv) в 1978 г. Этот алгоритм воспринимался как математическая абстракция до 1984 г., когда Терри Уэлч (Terry A. Welch) опубликовал свою работу с модифицированным алгоритмом, получившим в дальнейшем название LZW (Lempel—Ziv—Welch).
Применение
Опубликование алгоритма LZW произвело большое впечатление на всех специалистов по сжатию информации. За этим последовало большое количество программ и приложений с различными вариантами этого метода.
Этот метод позволяет достичь одну из наилучших степеней сжатия среди других существующих методов сжатия графических данных, при полном отсутствии потерь или искажений в исходных файлах. В настоящее время испольуется в файлах формата TIFF, PDF, GIF, PostScript и других, а также отчасти во многих популярных программах сжатия данных (ZIP, ARJ, LHA).
Описание
Процесс сжатия выглядит следующим образом: последовательно считываются символы входного потока и происходит проверка, существует ли в созданной таблице строк такая строка. Если такая строка существует, считывается следующий символ, а если строка не существует, в поток заносится код для предыдущей найденной строки, строка заносится в таблицу, а поиск начинается снова.
Например, если сжимают байтовые данные (текст), то строк в таблице окажется
(от до ). Если используется -битный код, то под коды для строк остаются значения в диапазоне от до заносится первый символ сообщения.- Шаг 2. Считать очередной символ из сообщения.
- Шаг 3. Если
- Если фраза уже имеется в словаре, то присвоить входной фразе значение и перейти к Шагу 2,
- Иначе выдать код для входной фразы , добавить в словарь и присвоить входной фразе значение . Перейти к Шагу 2.
— это символ конца сообщения, то выдать код для , иначе:
- Конец.
Декодирование
- Начало.
- Шаг 1. Все возможные символы заносятся в словарь. Во входную фразу заносится первый код декодируемого сообщения.
- Шаг 2. Считать очередной код из сообщения.
- Шаг 3. Если
- Если фразы под кодом нет в словаре, вывести фразу, соответствующую коду , а фразу с кодом занести в словарь.
- Иначе присвоить входной фразе код и перейти к Шагу 2.
— это конец сообщения, то выдать символ, соответствующий коду , иначе:
- Конец.
Пример
Рассмотрим пример сжатия и декодирования сообщения. Сначала создадим начальный словарь единичных символов. В стандартной кодировке ASCII имеется
различных символов, поэтому, для того, чтобы все они были корректно закодированы (если нам неизвестно, какие символы будут присутствовать в исходном файле, а какие - нет), начальный размер кода будет равен 8 битам. Если нам заранее известно, что в исходном файле будет меньшее количество различных символов, то вполне разумно уменьшить количество бит. Чтобы инициализировать таблицу, мы установим соответствие кода 0 соответствующему символу с битовым кодом , тогда соответствует символу с кодом , и т.д., до кода . На самом деле мы указали, что каждый код от до является корневым.Символ | Битовый код |
---|---|
a | 000 |
b | 001 |
c | 010 |
d | 011 |
e | 100 |
Больше в таблице не будет других кодов, обладающих этим свойством.
По мере роста словаря, размер групп должен расти, с тем, чтобы учесть новые элементы. -битные группы дают возможных комбинации бит, поэтому, когда в словаре появится -е слово, алгоритм должен перейти к -битным группам. При появлении -ого слова произойдет переход к -битным группам, что дает возможность запоминать уже слова и т.д.
В нашем примере алгоритму заранее известно о том, что будет использоваться всего
различных символов, следовательно, для их хранения будет использоваться минимальное количество бит, позволяющее нам их запомнить, то есть ( различных комбинаций).Кодирование
Пусть мы сжимаем последовательность
.- Шаг 1: Тогда, согласно изложенному выше алгоритму, мы добавим к изначально пустой строке и проверим, есть ли строка в таблице. Поскольку мы при инициализации занесли в таблицу все строки из одного символа, то строка есть в таблице.
- Шаг 2: Далее мы читаем следующий символ из входного потока и проверяем, есть ли строка в таблице. Такой строки в таблице пока нет.
Добавляем в таблицу
. В поток: ;- Шаг 3: — нет. В таблицу: . В поток: ;
- Шаг 4: — нет. В таблицу: . В поток: ;
- Шаг 5: — нет. В таблицу: . В поток: ;
- Шаг 6: — есть в таблице; — нет. В таблицу: . В поток: ;
- Шаг 7: — нет. В таблицу: . В поток: ;
- Шаг 8: — нет. В таблицу: . В поток: ;
- Шаг 9: — есть в таблице; — нет. В таблицу: . В поток: ;
- Шаг 10: — есть в таблице; — нет. В таблицу: . В поток: ;
- Шаг 11: — есть в таблице; — нет. В таблицу: . В поток: ;
- Шаг 12: И, наконец последняя строка , за ней идет конец сообщения, поэтому мы просто выводим в поток .
Текущая строка | Текущий символ | Следующий символ | Вывод | Словарь | ||
---|---|---|---|---|---|---|
Код | Биты | |||||
ab | a | b | 0 | 000 | 5: | ab |
ba | b | a | 1 | 001 | 6: | ba |
ac | a | c | 0 | 000 | 7: | ac |
ca | c | a | 2 | 010 | 8: | ca |
ab | a | b | - | - | - | - |
aba | b | a | 5 | 101 | 9: | aba |
ad | a | d | 0 | 000 | 10: | ad |
da | d | a | 3 | 011 | 11: | da |
ab | a | b | - | - | - | - |
aba | b | a | - | - | - | - |
abac | a | c | 9 | 1001 | 12: | abac |
ca | c | a | - | - | - | - |
cab | a | b | 8 | 1000 | 13: | cab |
ba | b | a | - | - | - | - |
bae | a | e | 6 | 0110 | 14: | bae |
e | e | - | 4 | 0100 | - | - |
Итак, мы получаем закодированное сообщение
.Каждый символ исходного сообщения был закодирован группой из трех бит, сообщение содержало 16 символов, следовательно длина сообщения составляла
бит.Закодированное же сообщение так же сначала кодировалось трехбитными группами, а про появлении в словаре восьмого слова - четырехбитными, итого длина сообщения составила
бит, что на бит короче исходного.Декодирование
Особенность LZW заключается в том, что для декомпрессии нам не надо сохранять таблицу строк в файл для распаковки. Алгоритм построен таким образом, что мы в состоянии восстановить таблицу строк, пользуясь только потоком кодов.
Теперь представим, что мы получили закодированное сообщение, приведённое выше, и нам нужно его декодировать. Прежде всего, нам нужно знать начальный словарь, а последующие записи словаря мы можем реконструировать уже на ходу, поскольку они являются просто конкатенацией предыдущих записей.
Данные | На выходе | Новая запись | ||||
---|---|---|---|---|---|---|
Биты | Код | Полная | Частичная | |||
000 | 0 | a | - | - | 5: | a? |
001 | 1 | b | 5: | ab | 6: | b? |
000 | 0 | a | 6: | ba | 7: | a? |
010 | 2 | c | 7: | ac | 8: | c? |
101 | 5 | ab | 8: | ca | 9: | ab? |
000 | 0 | a | 9: | aba | 10: | a? |
011 | 3 | d | 10: | ad | 11: | d? |
1001 | 9 | aba | 11: | da | 12: | aba? |
1000 | 8 | ca | 12: | abac | 13: | ca? |
0110 | 6 | ba | 13: | cab | 14: | ba? |
0100 | 4 | e | 14: | bae | - | - |
Примечание
Для повышения степени сжатия изображений данным методом часто используется одна «хитрость» реализации этого алгоритма. Некоторые файлы, подвергаемые сжатию с помощью LZW, имеют часто встречающиеся цепочки одинаковых символов, например
… или … и т. п. Их непосредственное сжатие будет генерировать выходной код и т.д. Спрашивается, можно ли в этом частном случае повысить степень сжатия?Оказывается, это возможно, если оговорить некоторые действия:
Мы знаем, что для каждого кода надо добавлять в таблицу строку, состоящую из уже присутствующей там строки и символа, с которого начинается следующая строка в потоке.
- Итак, кодировщик заносит первую в строку, ищет и находит в словаре. Добавляет в строку следующую , находит, что нет в словаре. Тогда он добавляет запись : в словарь и выводит метку ( ) в выходной поток.
- Далее строка инициализируется второй , то есть принимает вид вводится третья , строка вновь равна , которая теперь имеется в словаре.
- Если появляется четвертая , то строка равна , которой нет в словаре. Словарь пополняется этой строкой, а на выход идет метка ( ).
- После этого строка инициализируется третьей , и т.д. и т.п. Дальнейший процесс вполне ясен.
Текущая строка | Текущий символ | Следующий символ | Вывод | Словарь | ||
---|---|---|---|---|---|---|
Код | Биты | |||||
aa | a | a | 0 | 000 | 5: | aa |
aa | a | a | - | - | - | - |
aaa | a | a | 5 | 101 | 6: | aaa |
a | a | a | - | - | - | - |
aa | a | a | - | - | - | - |
aaa | a | - | 6 | 110 | - | - |
В результате на выходе получаем последовательность
..., которая короче прямого кодирования стандартным методом LZW.Можно показать, что такая последовательность будет корректно восстановлена. Декодировщик сначала читает первый код – это
, которому соответствует символ . Затем читает код , но этого кода в его таблице нет. Но мы уже знаем, что такая ситуация возможна только в том случае, когда добавляемый символ равен первому символу только что считанной последовательности, то есть . Поэтому он добавит в свою таблицу строку с кодом , а в выходной поток поместит . И так может быть раскодирована вся цепочка кодов.Мало того, описанное выше правило кодирования мы можем применять в общем случае не только к подряд идущим одинаковым символам, но и к последовательностям, у которых очередной добавляемый символ равен первому символу цепочки.
Преимущества алгоритма LZW
- Алгоритм является однопроходным.
- Для декомпрессии не надо сохранять таблицу строк в файл для распаковки. Алгоритм построен таким образом, что мы в состоянии восстановить таблицу строк, пользуясь только потоком кодов.
Недостатки алгоритма LZW
- Алгоритм не проводит анализ входных данных.