Алгоритм LZSS — различия между версиями
(→Оптимизации) |
м (rollbackEdits.php mass rollback) |
||
| (не показаны 62 промежуточные версии 4 участников) | |||
| Строка 1: | Строка 1: | ||
| − | Эта версия [[Алгоритмы LZ77 и LZ78|алгоритма LZ77]] была разработана Сторером (''Storer'') и Сжимански (''Szymanski'') в 1982. Базовый алгоритм был улучшен по трем направлениям:буфер, содержащий еще не закодированные символы, | + | Эта версия [[Алгоритмы LZ77 и LZ78|алгоритма LZ77]] была разработана Сторером (''Storer'') и Сжимански (''Szymanski'') в 1982. Базовый алгоритм был улучшен по трем направлениям: |
| + | *буфер, содержащий еще не закодированные символы, сохраняется в циклической [[Очередь|очереди]] | ||
| + | *буфер поиска (словарь) хранитсяся в виде [[Дерево поиска, наивная реализация|двоичного дерева поиска]] | ||
| + | *метки имеют два поля, а не три | ||
| + | == Модель данных == | ||
| − | == | + | ===Реализация "скольжения"=== |
| − | + | Как и в [[Алгоритмы LZ77 и LZ78|алгоритме LZ77]], в этом алгоритме используется обычный символьный буфер для хранения содержимого окна. В целях повышения эффективности "скольжения" окна по содержимому сообщения используется циклический буфер, размер которого идентичен размеру скользящего окна. Такой буфер организован на физически сплошном участке памяти, а не на реальном сдвиге содержимого окна, как в LZ77. Скольжение окна в таком буфере сводится к циклическому перемещению границы между его задней и передней частями (текущей обрабатываемой позиции) в пределах буфера. При этом добавление новой порции информации в переднюю часть окна автоматически означает удаление идентичной по длине порции из его задней части. | |
| − | |||
| − | + | Размер циклического буфера равен степени двойки, и стандартная для циклического буфера операция "смещение по модулю размер", может быть заменена побитовой логической операцией, что еще больше повышает эффективность. | |
| − | == | + | ===Организация поиска в словаре=== |
| − | |||
| − | |||
| − | |||
| − | + | Скорость кодирования [[Алгоритмы LZ77 и LZ78|LZ77]] сильно зависит от того, каким образом осуществляется поиск совпадающей подстроки в словаре. В LZSS при кодировании поддерживается бинарное лексикографически упорядоченное дерево поиска, в котором каждому узлу соответствует определенная строка словаря длины <tex>M</tex> (максимальная длина совпадения). В дереве хранятся все подстроки словарной части размером длины буфера. | |
| − | === | + | ====Порядок изменения дерева поиска==== |
| + | Кодер изучает буфер поиска, создавая <tex>T </tex> строк с числом символов <tex>L</tex> , которые помещены в двоичное лексикографически упорядоченное дерево поиска вместе с их смещениями. | ||
| + | На дереве все время находится одинаковое число <tex>T </tex> узлов или строк, поскольку при его обновлении удаляется и добавляется одно и то же число строк, <tex>T=S-L+1</tex>. | ||
| − | + | Если во время кодирования случается совпадение длины <tex>k</tex>, то дерево надо перестроить путем удаления <tex>k</tex> строк и добавления <tex>k</tex> строк. | |
| − | {| | + | Удаляться будут первые <tex>k</tex> строк буфера поиска до его сдвига, а добавляться будут последние <tex>k</tex> строк этого буфера после сдвига. |
| + | |||
| + | Простейшая процедура обновления дерева состоит в приготовлении строк из начала буфера, их поиска и удаления. Потом необходимо сдвинуть буфер на одну позицию вправо (или переместить данные на одну позицию влево), приготовить строку из последних <tex>L</tex> символов буфера поиска и добавить ее на дерево. Это следует повторить <tex>k</tex> раз. | ||
| + | |||
| + | Для определения смещения уславливаемся, что: | ||
| + | * нулевое смещение зарезервировали для обозначения конца кодирования; | ||
| + | * если имеется несколько фраз с одинаковой длиной совпадения, то выбираем ближайшую к буферу. | ||
| + | |||
| + | |||
| + | Покажем на примере, как в алгоритме LZSS происходит трансформация двоичного дерева, в виде которого хранится словарь. | ||
| + | |||
| + | ====Пример==== | ||
| + | Пусть входной файл содержит следующую последовательность: "sid_eastman_clumsily_teases_sea_sick_seals". Для простоты предположим, что окно (желтые ячейки в таблице) состоит из <tex>16</tex>-байтного буфера поиска и <tex>5</tex>-байтного буфера, содержащего еще не закодированные символы. После ввода первых <tex>16+5</tex> символов скользящее окно выглядит так: | ||
| + | |||
| + | {| class="wikitable" | ||
|style="background:#FFCC00" |sid_eastman_clum | |style="background:#FFCC00" |sid_eastman_clum | ||
|style="background:#FFCC00"|sily_ | |style="background:#FFCC00"|sily_ | ||
| Строка 27: | Строка 43: | ||
причем подпоследовательность teases_sea_sick_seals ждет своей очереди на входе. | причем подпоследовательность teases_sea_sick_seals ждет своей очереди на входе. | ||
| − | Кодер изучает буфер поиска, создавая двенадцать строк по пять символов (см. табл.) (их двенадцать, так как < | + | Кодер изучает буфер поиска, создавая двенадцать строк по пять символов (см. табл.1) (их двенадцать, так как <tex>12=16-5+1</tex>), которые помещены на двоичное дерево поиска вместе с их смещениями. |
| − | |||
| − | + | '''Таблицы 1 и 2. Строки по пять символов.''' | |
| − | + | {| | |
| − | + | | | |
| − | + | {| class="wikitable" | |
| − | + | ! colspan="2" |Таблица 1 | |
| − | |||
| − | {| | ||
| − | | | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
|- | |- | ||
| sid_e | | sid_e | ||
| Строка 85: | Строка 87: | ||
|05 | |05 | ||
|} | |} | ||
| + | | || | ||
| + | | | ||
| + | {| class="wikitable" | ||
| + | ! colspan="2" |Таблица 2 | ||
| + | |- | ||
| + | |d_eas | ||
| + | |16 | ||
| + | |- | ||
| + | |_east | ||
| + | |15 | ||
| + | |- | ||
| + | |eastm | ||
| + | |14 | ||
| + | |- | ||
| + | |astma | ||
| + | |13 | ||
| + | |- | ||
| + | |stmаn | ||
| + | |12 | ||
| + | |- | ||
| + | |tman_ | ||
| + | |11 | ||
| + | |- | ||
| + | |man_c | ||
| + | |10 | ||
| + | |- | ||
| + | |an_cl | ||
| + | |09 | ||
| + | |- | ||
| + | |n_clu | ||
| + | |08 | ||
| + | |- | ||
| + | |_clum | ||
| + | |07 | ||
| + | |- | ||
| + | | clums | ||
| + | |06 | ||
| + | |- | ||
| + | | lumsi | ||
| + | |05 | ||
| + | |} | ||
| + | |} | ||
| − | + | Первым символом в буфере, содержащем еще не закодированные символы, является s, поэтому кодер ищет на дереве строки, начинающиеся на s. Он находит две строки со смещениями <tex>16</tex> и <tex>10</tex>, но первая из них, sid_e, имеет более длинное совпадение. | |
| + | Бывают случаи, когда строка на дереве полностью совпадает с содержимым буфера, содержащего еще не закодированные символы. Тогда кодер может искать дальнейшие совпадения. В принципе, длина совпадения может быть <tex>L-1</tex>. | ||
| + | |||
| + | В нашем примере длина совпадения равна <tex>2</tex>, поэтому кодер выдает метку <tex>\langle</tex><tex>16,2</tex><tex>\rangle</tex>. Теперь кодер должен переместить скользящее окно на две позиции вправо и перестроить дерево. Новое окно выглядит следующим образом: | ||
| + | |||
| + | {| class="wikitable" | ||
| + | |si | ||
| + | |style="background:#FFCC00"|d_eastman_clumsi | ||
| + | |style="background:#FFCC00"|ly_te | ||
| + | |ases_sea_sick_seals | ||
| + | |} | ||
| + | С дерева необходимо удалить строки sid_e и id_ea и вставить новые строки clums и lumsi (см. табл.2). | ||
| + | Для того чтобы найти эти строки, можно просмотреть все вершины дерева и выбрать те пары, где разница между смещением и длиной буфера не превосходит длины части совпадения. | ||
| + | В данном примере длина буфера {{---}} <tex>5</tex> символов, часть совпадения {{---}} <tex>2</tex> символа, поэтому нужно найти смещения где разница между ним и длиной буфера не превосходит <tex>2</tex>. Это смещения <tex>5</tex> и <tex>6</tex>. Теперь для каждой строки в дереве нужно удалить первые два символа и прибавить следующие два символа. | ||
| + | |||
| + | ===Оптимизация памяти=== | ||
| + | |||
| + | У алгоритма [[Алгоритмы LZ77 и LZ78|LZ77]] возникают проблемы с самим сжатием. Они появляются, когда кодер не может найти совпадающую подстроку в словаре и выдает стандартный 3-компонентный код, пытаясь закодировать один символ. Такое кодирование существенно понижает производительность алгоритма. | ||
| + | |||
| + | Метка LZSS состоит только из смещения и длины. Проблема отсутствия совпадений в словаре в алгоритме LZSS решается путем введения дополнительного служебного бита (со значением "0" для незакодированных символов и "1" для кодовых комбинаций), значение которого определяет, является ли следующая за ним кодовая комбинация кодовой парой или она представляет собой незакодированный символ в его исходном представлении. Такая техника позволяет записывать символы в явном виде, когда соответствующий им код имеет большую длину, а также позволяет обрабатывать ни разу не встреченные до текущего момента символы. | ||
== Кодер LZSS == | == Кодер LZSS == | ||
| Строка 94: | Строка 157: | ||
Алгоритм последовательно выполняет следующие действия: | Алгоритм последовательно выполняет следующие действия: | ||
| − | # | + | # Кодирует содержимое буфера. |
| − | # | + | # Считывает очередные символы в буфер, удаляя при необходимости наиболее "старые" строки из словаря. |
| − | # | + | # Вставляет в дерево новые строки, соответствующие считанным символам. |
| + | |||
===Завершение работы=== | ===Завершение работы=== | ||
| − | Для того чтобы декодер смог вовремя остановиться, декодируя сжатое сообщение, кодер помещает в сжатый файл специальный символ | + | Для того чтобы декодер смог вовремя остановиться, декодируя сжатое сообщение, кодер помещает в сжатый файл специальный символ "КОНЕЦ ФАЙЛА" после того, как он обработал все символы сообщения. |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
===Пример кодирования=== | ===Пример кодирования=== | ||
| + | Закодировать по алгоритму LZSS строку "КРАСНАЯ КРАСКА". | ||
| + | В данном примере один символ кодируется восемью битами. Поэтому после префикса <tex>0</tex> декодер считывает следующие <tex>8</tex> бит. | ||
| + | После префикса <tex>1</tex> хранится пара <tex>\langle</tex><tex>offset,length</tex><tex>\rangle</tex>. Так как смещение и длина не превосходят длины словаря, то размер пары <tex>\langle</tex><tex>offset,length</tex><tex>\rangle</tex> равен удвоенному количеству бит, требуемых для хранения длины словаря. | ||
| + | Длина словаря в данном примере <tex>8</tex>. Поэтому после префикса <tex>1</tex> декодер считывает <tex>6</tex> бит. | ||
| − | |||
[[Файл:540e164e73fca7dcfc2b6c970b135e24.png]] | [[Файл:540e164e73fca7dcfc2b6c970b135e24.png]] | ||
| + | |||
==Декодер LZSS== | ==Декодер LZSS== | ||
Алгоритм LZSS, вообще говоря, является очень асимметричным. Если процедура сжатия достаточно сложна и совершает большой объем работы при обработке каждого символа сжимаемого сообщения, то декодер LZSS тривиально прост и может работать со скоростью, приближающейся к скорости процедуры обычного копирования информации. | Алгоритм LZSS, вообще говоря, является очень асимметричным. Если процедура сжатия достаточно сложна и совершает большой объем работы при обработке каждого символа сжимаемого сообщения, то декодер LZSS тривиально прост и может работать со скоростью, приближающейся к скорости процедуры обычного копирования информации. | ||
| − | Декодер читает один бит сжатой информации и решает — это символ или пара < | + | Декодер читает один бит сжатой информации и решает — это символ или пара <tex>\langle</tex><tex>offset,length</tex><tex>\rangle</tex>. Если это символ, то следующие <tex>8</tex> бит выдаются как раскодированный символ и помещаются в скользящее окно. Иначе, если это не закодированный конец файла, то соответствующее количество символов словаря помещается в окно и выдается в раскодированном виде. Поскольку это все, что делает декодер, понятно, почему процедура декодирования работает так быстро. |
===Пример декодирования=== | ===Пример декодирования=== | ||
| − | LZSS, длина словаря — 8 байт (символов). Коды сжатого сообщения — | + | LZSS, длина словаря — <tex>8</tex> байт (символов). Коды сжатого сообщения — |
[[Файл:Кодисходного_сообщения_png.png ]] | [[Файл:Кодисходного_сообщения_png.png ]] | ||
| Строка 137: | Строка 191: | ||
например в PKZIP V.1.0 использован LZSS в комбинации с алгоритмом Шеннона-Фано, а в | например в PKZIP V.1.0 использован LZSS в комбинации с алгоритмом Шеннона-Фано, а в | ||
ARJ — LZSS с [[Алгоритм Хаффмана|алгоритмом Хаффмана]]. | ARJ — LZSS с [[Алгоритм Хаффмана|алгоритмом Хаффмана]]. | ||
| + | |||
| + | == См.также == | ||
| + | * [[Алгоритмы LZ77 и LZ78]] | ||
| + | * [[Алгоритм LZW]] | ||
== Источники информации == | == Источники информации == | ||
| Строка 149: | Строка 207: | ||
*[http://www.intuit.ru/studies/courses/2256/140/lecture/3914?page=1/ Подстановочные или словарно-ориентированные алгоритмы сжатия информации. Методы Лемпела-Зива] | *[http://www.intuit.ru/studies/courses/2256/140/lecture/3914?page=1/ Подстановочные или словарно-ориентированные алгоритмы сжатия информации. Методы Лемпела-Зива] | ||
| − | |||
| − | |||
| − | |||
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Алгоритмы сжатия ]] | [[Категория: Алгоритмы сжатия ]] | ||
Текущая версия на 19:29, 4 сентября 2022
Эта версия алгоритма LZ77 была разработана Сторером (Storer) и Сжимански (Szymanski) в 1982. Базовый алгоритм был улучшен по трем направлениям:
- буфер, содержащий еще не закодированные символы, сохраняется в циклической очереди
- буфер поиска (словарь) хранитсяся в виде двоичного дерева поиска
- метки имеют два поля, а не три
Модель данных
Реализация "скольжения"
Как и в алгоритме LZ77, в этом алгоритме используется обычный символьный буфер для хранения содержимого окна. В целях повышения эффективности "скольжения" окна по содержимому сообщения используется циклический буфер, размер которого идентичен размеру скользящего окна. Такой буфер организован на физически сплошном участке памяти, а не на реальном сдвиге содержимого окна, как в LZ77. Скольжение окна в таком буфере сводится к циклическому перемещению границы между его задней и передней частями (текущей обрабатываемой позиции) в пределах буфера. При этом добавление новой порции информации в переднюю часть окна автоматически означает удаление идентичной по длине порции из его задней части.
Размер циклического буфера равен степени двойки, и стандартная для циклического буфера операция "смещение по модулю размер", может быть заменена побитовой логической операцией, что еще больше повышает эффективность.
Организация поиска в словаре
Скорость кодирования LZ77 сильно зависит от того, каким образом осуществляется поиск совпадающей подстроки в словаре. В LZSS при кодировании поддерживается бинарное лексикографически упорядоченное дерево поиска, в котором каждому узлу соответствует определенная строка словаря длины (максимальная длина совпадения). В дереве хранятся все подстроки словарной части размером длины буфера.
Порядок изменения дерева поиска
Кодер изучает буфер поиска, создавая строк с числом символов , которые помещены в двоичное лексикографически упорядоченное дерево поиска вместе с их смещениями. На дереве все время находится одинаковое число узлов или строк, поскольку при его обновлении удаляется и добавляется одно и то же число строк, .
Если во время кодирования случается совпадение длины , то дерево надо перестроить путем удаления строк и добавления строк.
Удаляться будут первые строк буфера поиска до его сдвига, а добавляться будут последние строк этого буфера после сдвига.
Простейшая процедура обновления дерева состоит в приготовлении строк из начала буфера, их поиска и удаления. Потом необходимо сдвинуть буфер на одну позицию вправо (или переместить данные на одну позицию влево), приготовить строку из последних символов буфера поиска и добавить ее на дерево. Это следует повторить раз.
Для определения смещения уславливаемся, что:
- нулевое смещение зарезервировали для обозначения конца кодирования;
- если имеется несколько фраз с одинаковой длиной совпадения, то выбираем ближайшую к буферу.
Покажем на примере, как в алгоритме LZSS происходит трансформация двоичного дерева, в виде которого хранится словарь.
Пример
Пусть входной файл содержит следующую последовательность: "sid_eastman_clumsily_teases_sea_sick_seals". Для простоты предположим, что окно (желтые ячейки в таблице) состоит из -байтного буфера поиска и -байтного буфера, содержащего еще не закодированные символы. После ввода первых символов скользящее окно выглядит так:
| sid_eastman_clum | sily_ | teases_sea_sick_seals |
причем подпоследовательность teases_sea_sick_seals ждет своей очереди на входе.
Кодер изучает буфер поиска, создавая двенадцать строк по пять символов (см. табл.1) (их двенадцать, так как ), которые помещены на двоичное дерево поиска вместе с их смещениями.
Таблицы 1 и 2. Строки по пять символов.
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Первым символом в буфере, содержащем еще не закодированные символы, является s, поэтому кодер ищет на дереве строки, начинающиеся на s. Он находит две строки со смещениями и , но первая из них, sid_e, имеет более длинное совпадение. Бывают случаи, когда строка на дереве полностью совпадает с содержимым буфера, содержащего еще не закодированные символы. Тогда кодер может искать дальнейшие совпадения. В принципе, длина совпадения может быть .
В нашем примере длина совпадения равна , поэтому кодер выдает метку . Теперь кодер должен переместить скользящее окно на две позиции вправо и перестроить дерево. Новое окно выглядит следующим образом:
| si | d_eastman_clumsi | ly_te | ases_sea_sick_seals |
С дерева необходимо удалить строки sid_e и id_ea и вставить новые строки clums и lumsi (см. табл.2). Для того чтобы найти эти строки, можно просмотреть все вершины дерева и выбрать те пары, где разница между смещением и длиной буфера не превосходит длины части совпадения. В данном примере длина буфера — символов, часть совпадения — символа, поэтому нужно найти смещения где разница между ним и длиной буфера не превосходит . Это смещения и . Теперь для каждой строки в дереве нужно удалить первые два символа и прибавить следующие два символа.
Оптимизация памяти
У алгоритма LZ77 возникают проблемы с самим сжатием. Они появляются, когда кодер не может найти совпадающую подстроку в словаре и выдает стандартный 3-компонентный код, пытаясь закодировать один символ. Такое кодирование существенно понижает производительность алгоритма.
Метка LZSS состоит только из смещения и длины. Проблема отсутствия совпадений в словаре в алгоритме LZSS решается путем введения дополнительного служебного бита (со значением "0" для незакодированных символов и "1" для кодовых комбинаций), значение которого определяет, является ли следующая за ним кодовая комбинация кодовой парой или она представляет собой незакодированный символ в его исходном представлении. Такая техника позволяет записывать символы в явном виде, когда соответствующий им код имеет большую длину, а также позволяет обрабатывать ни разу не встреченные до текущего момента символы.
Кодер LZSS
Инициализация
Для того чтобы кодер мог начать работать, необходимо загрузить буфер очередными символами сообщения и проинициализировать дерево. Для этого в дерево вставляется содержимое буфера.
Основной цикл работы
Алгоритм последовательно выполняет следующие действия:
- Кодирует содержимое буфера.
- Считывает очередные символы в буфер, удаляя при необходимости наиболее "старые" строки из словаря.
- Вставляет в дерево новые строки, соответствующие считанным символам.
Завершение работы
Для того чтобы декодер смог вовремя остановиться, декодируя сжатое сообщение, кодер помещает в сжатый файл специальный символ "КОНЕЦ ФАЙЛА" после того, как он обработал все символы сообщения.
Пример кодирования
Закодировать по алгоритму LZSS строку "КРАСНАЯ КРАСКА". В данном примере один символ кодируется восемью битами. Поэтому после префикса декодер считывает следующие бит. После префикса хранится пара . Так как смещение и длина не превосходят длины словаря, то размер пары равен удвоенному количеству бит, требуемых для хранения длины словаря. Длина словаря в данном примере . Поэтому после префикса декодер считывает бит.
Декодер LZSS
Алгоритм LZSS, вообще говоря, является очень асимметричным. Если процедура сжатия достаточно сложна и совершает большой объем работы при обработке каждого символа сжимаемого сообщения, то декодер LZSS тривиально прост и может работать со скоростью, приближающейся к скорости процедуры обычного копирования информации.
Декодер читает один бит сжатой информации и решает — это символ или пара . Если это символ, то следующие бит выдаются как раскодированный символ и помещаются в скользящее окно. Иначе, если это не закодированный конец файла, то соответствующее количество символов словаря помещается в окно и выдается в раскодированном виде. Поскольку это все, что делает декодер, понятно, почему процедура декодирования работает так быстро.
Пример декодирования
LZSS, длина словаря — байт (символов). Коды сжатого сообщения —
Практическое использование алгоритма LZSS
Так как алгоритм LZSS не является запатентованным, он широко используется. LZSS можно удачно скомбинировать с методами сжатия, основанными на переменной длине кода (Алгоритм Хаффмана, алгоритм Шеннона-Фано (Shannon-Fano)) например в PKZIP V.1.0 использован LZSS в комбинации с алгоритмом Шеннона-Фано, а в ARJ — LZSS с алгоритмом Хаффмана.
См.также
Источники информации
- Д.Сэломон. Сжатие данных, изображений и звука - Москва: Техносфера, 2004. - с.368, стр. 88.
- Семенюк В. В. Экономное кодирование дискретной информации. – СПб.: СПбГИТМО (ТУ), 2001. – 115 с., стр.60-62.
- Лидовский В.В. Теория информации: учебное пособие. – Москва: 2003. – 112 с., с. 37-38.

