Словарные методы сжатия данных
Пример
Попробуем сжать строку "кот_ломом_колол_слона" длиной 21 символ. Пусть длина буфера равна 7 символам, а размер словаря больше длины сжимаемой строки. Условимся также, что:
- нулевое смещение зарезервировали для обозначения конца кодирования;
- символ
соответствует единичному смещению относительно символа
, с которого начинается буфер; - если имеется несколько фраз с одинаковой длиной совпадения, то выбираем ближайшую к буферу;
- в неопределенных ситуациях - когда длина совпадения нулевая - смещению присваиваем единичное значение.
| Шаг | Скользящее окно | Совпадающая фраза | Закодированные данные | |||
|---|---|---|---|---|---|---|
| Словарь | Буфер | i | j | s | ||
| 1 | - | кот_лом | - | 1 | 0 | 'к' |
| 2 | к | от_ломо | - | 1 | 0 | 'о' |
| 3 | ко | т_ломом | - | 1 | 0 | 'т' |
| 4 | кот | _ломом_ | - | 1 | 0 | '_' |
| 5 | кот_ | ломом_к | - | 1 | 0 | 'л' |
| 6 | кот_л | омом_ко | о | 4 | 1 | 'м' |
| 7 | кот_лом | ом_коло | ом | 2 | 2 | '_' |
| 8 | кот_ломом_ | колол_с | ко | 10 | 2 | 'л' |
| 9 | кот_ломом_кол | ол_слон | ол | 2 | 2 | '_' |
| 10 | ..._ломом_колол_ | слона | - | 1 | 0 | 'с' |
| 11 | ...ломом_колол_с | лона | ло | 5 | 2 | 'н' |
| 12 | ...ом_колол_слон | а | - | 1 | 0 | 'а' |
Для кодирования i нам достаточно 5 битов, для j нужно 3 бита, и пусть символы требуют 1 байта для своего представления. Тогда всего мы потратим 12·(5+3+8) = 192 бита. Исходно строка занимала 21·8 = 168 битов, т.е. LZ77 кодирует нашу строку еще более расточительным образом. Не следует также забывать, что мы опустили шаг кодирования конца последовательности, который потребовал бы еще как минимум 5 битов (размер поля i = 5 битам).
Процесс кодирования можно описать следующим образом.
while ( ! DataFile.EOF() ){
/*найдем максимальное совпадение, в match_pos получим
смещение i, в match_len - длину j, в unmatched_sym
- первый несовпавший символ st+1+j; считаем также, что в
функции find_match учитывается ограничение на длину
совпадения
*/
find_match (&match_pos, &match_len, &unmatched_sym);
/*запишем в файл сжатых данных описание найденной
фразы, при этом длина битового представления i
задается константой OFFS_LN, длина представления
j - константой LEN_LN, размер символа s принимаем
равным 8 битам
*/
CompressedFile.WriteBits (match_pos, OFFS_LN);
CompressedFile.WriteBits (match_len, LEN_LN);
CompressedFile.WriteBits (unmatched_sym, 8);
for (i = 0; i <= match_len; i++){
// прочтем очередной символ
c = DataFile.ReadSymbol();
//удалим из словаря одну самую старую фразу
DeletePhrase ();
/*добавим в словарь одну фразу, начинающуюся с
первого символа буфера
*/
AddPhrase ();
/*сдвинем окно на 1 позицию, добавим в конец буфера
символ с
*/
MoveWindow(c);
}
}
CompressedFile.WriteBits (0, OFFS_LN);
Пример
2.1.
Пример подтвердил, что способ формирования кодов в LZ77 неэффективен и позволяет сжимать только сравнительно длинные последовательности. До некоторой степени сжатие небольших файлов можно улучшить, используя коды переменной длины для смещения i. Действительно, даже если мы используем словарь в 32 кбайт, но закодировали еще только 3 кбайт, то смещение реально требует не 15, а 12 битов. Кроме того, происходит существенный проигрыш из-за использования кодов одинаковой длины при указании длин совпадения j. Например, для уже упоминавшейся электронной версии романа "Бесы" были получены следующие частоты использования длин совпадения (см. табл. 2.4):
| j | Количество раз, когда максимальная длина совпадения была равна j |
|---|---|
| 0 | 136 |
| 1 | 1593 |
| 2 | 4675 |
| 3 | 11165 |
| 4 | 20047 |
| 5 | 26939 |
| 6 | 28653 |
| 7 | 24725 |
| 8 | 19702 |
| 9 | 14767 |
| 10 | 10820 |
11 |
27903 |
Из таблицы следует, что в целях минимизации закодированного представления для j = 6 следует использовать код наименьшей длины, так как эта длина совпадения встречается чаще всего.
Хотя авторы алгоритма и доказали, что LZ77 может сжать данные не хуже, чем любой специально на них настроенный полуадаптивный словарный метод, из-за указанных недостатков это выполняется только для последовательностей достаточно большого размера.
Что касается декодирования сжатых данных, то оно осуществляется путем простой замены кода на блок символов, состоящий из фразы словаря и явно передаваемого символа. Естественно, декодер должен выполнять те же действия по изменению окна, что и кодер. Фраза словаря элементарно определяется по смещению и длине, поэтому важным свойством LZ77 и прочих алгоритмов со скользящим окном является очень быстрая работа декодера.
Алгоритм декодирования может иметь следующий вид.
for (;;) {
// читаем смещение
match_pos = CompressedFile.ReadBits (OFFS_LN);
if (!match_pos)
// обнаружен признак конца файла, выходим из цикла
break;
// читаем длину совпадения
match_len = CompressedFile.ReadBits (LEN_LN);
for (i = 0; i < match_len; i++) {
//находим в словаре очередной символ совпавшей фразы
c = Dict (match_pos + i);
/*сдвигаем словарь на 1 позицию, добавляем в его начало с */
MoveDict (c)
/*записываем очередной раскодированный символ в
выходной файл */
DataFile.WriteSymbol (c);
}
/*читаем несовпавший символ, добавляем его в словарь и
записываем в выходной файл
*/
c = CompressedFile.ReadBits (8);
MoveDict (c)
DataFile.WriteSymbol (c);
}Алгоритмы со скользящим окном характеризуются сильной несимметричностью по времени - кодирование значительно медленнее декодирования, поскольку при сжатии много времени тратится на поиск фраз.
11