Алгоритмы LZ77 и LZ78 — различия между версиями
Chavit (обсуждение | вклад) (→Пример "kabababababz") |
м (rollbackEdits.php mass rollback) |
||
| (не показаны 43 промежуточные версии 11 участников) | |||
| Строка 1: | Строка 1: | ||
| − | + | <tex>\mathrm{LZ77}</tex> и <tex>\mathrm{LZ78}</tex> {{---}} алгоритмы сжатия без потерь, опубликованные Абрахамом Лемпелем<ref>[https://en.wikipedia.org/wiki/Abraham_Lempel Wikipedia {{---}} Abraham Lempel]</ref> и Якобом Зивом<ref>[https://en.wikipedia.org/wiki/Yaakov_Ziv Wikipedia {{---}} Yaakov Ziv]</ref> в <tex>1977</tex> и <tex>1978</tex> годах соответственно. Эти алгоритмы стали основой других алгоритмов семьи <tex>\mathrm{LZ*}</tex>: [[Алгоритм_LZW|LZW]], [[Алгоритм_LZSS|LZSS]], [[Алгоритм_LZMA|LZMA]] и другие. Оба приведенных алгоритма используют словарный подход. | |
| − | Оба алгоритма | ||
== LZ77 == | == LZ77 == | ||
| − | + | ||
| − | === | + | === Идея алгоритма === |
| − | + | В кодируемых строках часто содержатся совпадающие длинные подстроки. Идея, лежащая в основе <tex>\mathrm{LZ77}</tex>, заключается в замене повторений на ссылки на позиции в тексте, где такие подстроки уже встречались. | |
| − | + | ||
| − | + | Информацию о повторении можно закодировать парой чисел {{---}} смещением назад от текущей позиции (<tex>offset</tex>) и длиной совпадающей подстроки (<tex>length</tex>). В таком случае, например, строка <tex>pabcdeqabcde</tex> может быть представлена как <tex>pabcdeq \langle 6, 5 \rangle</tex>. Выражение <tex>\langle 6, 5 \rangle</tex> означает «вернись на <tex>6</tex> символов назад и выведи <tex>5</tex> символов». | |
| − | + | ||
| − | + | Алгоритм <tex>\mathrm{LZ77}</tex> кодирует ссылки блоками из трёх элементов {{---}} <tex>\langle offset, length, next \rangle</tex>. В дополнение к двум уже описанным элементам, новый параметр <tex>next</tex> означает первый символ после найденного совпадающего фрагмента. Если <tex>\mathrm{LZ77}</tex> не удалось найти совпадение, то считается, что <tex>offset = length = 0</tex>. | |
| − | + | ||
| − | + | [[Файл:LZ77-simple-example.jpg]] | |
| − | + | ||
| − | + | Для эффективного поиска повторов в <tex>\mathrm{LZ77}</tex> применяется метод «скользящего окна» {{---}} совпадения ищутся не на всём обработанном префиксе, а в небольшом буфере, состоящем из последних обработанных символов. Обычно длина буфера равна <tex>2</tex>, <tex>4</tex> или <tex>32 \mathrm{KB}</tex>. Таким образом, при больших объемах ввода алгоритм тратит меньше времени за счет того, что просматривается не вся исходная строка. С другой стороны, слишком маленькая длина словаря может привести к тому, что расположенные далеко друг от друга совпадения (на большем расстоянии, чем длина словаря) не будут учтены, и кодирование станет менее оптимальным. | |
| − | + | ||
| − | Это означает, что каждый символ можно однозначно | + | Также нетривиальным может оказаться то, что при кодировании <tex>\mathrm{LZ77}</tex> значение <tex>offset</tex> может быть меньше, чем <tex>length</tex> (например, «вернись на <tex>2</tex> символа назад и выведи <tex>10</tex> символов»). Это означает, что подстрока будет повторяться несколько раз так, чтобы каждый символ совпадал с символом, стоящим на <tex>2</tex> позиции раньше, и всего добавилось <tex>10</tex> символов. Например: <tex>hello\langle 2, 10 \rangle \Leftrightarrow hello\boldsymbol{lololololo}</tex>. Символ всегда можно определить однозначно, даже если он отсутствует в буфере. |
| − | === | + | |
| − | + | === Реализация === | |
| − | + | ||
| − | + | Хранить результат кодирования будем в списке структур следующего вида: | |
| − | + | ||
| − | + | <code> | |
| − | === Пример | + | '''struct''' Node: |
| − | {| | + | '''int''' offset |
| − | + | '''int''' length | |
| − | + | '''char''' next | |
| − | + | </code> | |
| − | + | ||
| − | + | Функция <code>findMatching</code> ищет в буфере строку, совпадающую с префиксом суффикса строки <tex>s</tex>, начинающегося с символа <tex>pos</tex> и возвращает искомую пару <tex>\langle offset, length \rangle</tex>. | |
| − | + | ||
| − | + | Функция <code>shiftBuffer</code> сдвигает буфер вперёд на <tex>x</tex> символов. | |
| − | + | ||
| − | + | <code> | |
| − | + | <font color=green>// <tex>s</tex> {{---}} исходная строка | |
| − | + | // функция возвращает список блоков <tex>\langle offset, length, next \rangle</tex></font> | |
| − | + | '''list<Node>''' encodeLZ77('''string''' s): | |
| − | + | '''list<Node>''' ans = [] | |
| − | + | pos = 0 | |
| − | + | '''while''' pos < s.length: | |
| − | + | offset, length = findMatching(buffer, pos) <font color=green>// ищем слово в словаре</font> | |
| − | + | shiftBuffer(length + 1) <font color=green>// перемещаем скользящее окно</font> | |
| − | + | pos += length | |
| − | + | ans.push({offset, length, s[pos]}) <font color=green>// добавляем в ответ очередной блок</font> | |
| − | + | '''return''' ans | |
| − | + | </code> | |
| − | + | ||
| − | + | === Пример === | |
| − | + | ||
| + | Рассмотрим пример кодирования <tex>\mathrm{LZ77}</tex> на строке <tex>abacabacabadaca</tex> с буфером размера <tex>5</tex>. Квадратом обведен буфер. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | ! Строка || Совпадение || Закодированная последовательность || Примечание | ||
| + | |- | ||
| + | | <tex>abacabacabadaca</tex> || {{---}} || <tex>\langle 0, 0, a \rangle</tex> || Буфер пуст | ||
| + | |- | ||
| + | | <tex>\fbox{$a$}bacabacabadaca</tex> || {{---}} || <tex>\langle 0, 0, b \rangle</tex> || В буфере нет <tex>b</tex> | ||
| + | |- | ||
| + | | <tex>\fbox{$ab$}acabacabadaca</tex> || <tex>a</tex> || <tex>\langle 2, 1, c \rangle</tex> || | ||
| + | |- | ||
| + | | <tex>\fbox{$abac$}abacabadaca</tex> || <tex>abacaba</tex> || <tex>\langle 4, 7, d \rangle</tex> || Здесь выгодно сделать так, что <tex>offset < length</tex> | ||
| + | |- | ||
| + | | <tex>abacaba\fbox{$cabad$}aca</tex> || <tex>a</tex> || <tex>\langle 2, 1, c \rangle</tex> || Последовательность <tex>aca</tex> уже встречалась, но она находится за пределами окна, и <tex>\mathrm{LZ77}</tex> её не находит | ||
| + | |- | ||
| + | | <tex>abacabaca\fbox{$badac$}a</tex> || <tex>a</tex> || <tex>\langle 2, 1, \varnothing \rangle</tex> || Символов в строке больше нет, поэтому в качестве <tex>next</tex> ставим символ конца строки | ||
| + | |} | ||
| + | |||
| + | Результатом кодирования является список полученных троек: <tex>\left[ \langle 0, 0, a \rangle, \langle 0, 0, b \rangle, \langle 2, 1, c \rangle, \langle 4, 7, d \rangle, \langle 2, 1, c \rangle, \langle 2, 1, \varnothing \rangle \right]</tex>. | ||
| + | |||
| + | === Декодирование === | ||
| + | Для декодирования <tex>\mathrm{LZ77}</tex> необходимо пройти по уже раскодированной строке назад, вывести необходимую последовательность, затем следующий символ. | ||
| + | |||
| + | Псевдокод этого алгоритма: | ||
| + | |||
| + | <code> | ||
| + | <font color=green>// <tex>encoded</tex> {{---}} список блоков <tex>\langle offset, length, next \rangle</tex> | ||
| + | // функция возвращает декодированную строку</font> | ||
| + | '''string''' decodeLZ77('''list<Node>''' encoded): | ||
| + | ans = "" | ||
| + | '''for''' node '''in''' encoded: | ||
| + | '''if''' node.length > 0: <font color=green>// если необходим повтор</font> | ||
| + | start = ans.length - node.offset <font color=green>// возвращаемся на <tex>offset</tex> символов назад</font> | ||
| + | '''for''' i = 0 .. node.length - 1: <font color=green>// добавляем <tex>length</tex> символов</font> | ||
| + | ans += ans[start + i] | ||
| + | ans += node.next <font color=green>// добавляем следующий символ</font> | ||
| + | '''return''' ans | ||
| + | </code> | ||
| + | |||
| + | === Модификации === | ||
| + | Известно много различных модификаций алгоритма <tex>\mathrm{LZ77}</tex>. Например, вместо блоков можно хранить отдельно одиночные символы и отдельно {{---}} пары <tex>\langle offset, length \rangle</tex> (length-distance pair<ref>[https://en.wikipedia.org/wiki/LZ77_and_LZ78#LZ77 Wikipedia {{---}} LZ77]</ref>). [[Алгоритм LZSS]] является улучшением <tex>\mathrm{LZ77}</tex> и хранит однобитный флаг, показывающий, какие данные за ним идут: одиночный символ или пара смещения и длины. В формате <tex>\mathrm{PalmDoc}</tex><ref>[https://wiki.mobileread.com/wiki/PalmDOC Описание формата PalmDOC]</ref> на каждую пару смещения и длины выделяется по <tex>2</tex> байта. | ||
| + | |||
| + | Также на <tex>\mathrm{LZ77}</tex> основаны алгоритмы <tex>\mathrm{LZH}</tex><ref>[https://msdn.microsoft.com/en-us/library/hh554076.aspx LZH]</ref> и <tex>\mathrm{DEFLATE}</tex><ref>[https://en.wikipedia.org/wiki/DEFLATE Wikipedia {{---}} DEFLATE]</ref> (последний является широко используемым), которые совмещают <tex>\mathrm{LZ77}</tex> с [[Алгоритм Хаффмана|алгоритмом Хаффмана]], кодируя элементы пар и одиночные символы. | ||
| + | |||
== LZ78 == | == LZ78 == | ||
| − | + | === Идея алгоритма === | |
| + | Алгоритм <tex>\mathrm{LZ78}</tex> имеет немного другую идею: этот алгоритм в явном виде использует словарный подход, генерируя временный словарь во время кодирования и декодирования. | ||
| + | |||
| + | Изначально словарь пуст, а алгоритм пытается закодировать первый символ. На каждой итерации мы пытаемся увеличить кодируемый префикс, пока такой префикс есть в словаре. Кодовые слова такого алгоритма будут состоять из двух частей {{---}} номера в словаре самого длинного найденного префикса (<tex>pos</tex>) и символа, который идет за этим префиксом (<tex>next</tex>). При этом после кодирования такой пары префикс с приписанным символом добавляется в словарь, а алгоритм продолжает кодирование со следующего символа. | ||
| + | |||
| + | === Реализация === | ||
| + | |||
| + | Будем хранить результат кодирования в такой структуре: | ||
| + | |||
| + | <code> | ||
| + | '''struct''' Node: | ||
| + | '''int''' pos <font color=green>// номер слова в словаре</font> | ||
| + | '''char''' next | ||
| + | </code> | ||
| + | |||
| + | В качестве словаря будем использовать обычный <code>map</code>: | ||
| + | |||
| + | <code> | ||
| + | '''list<Node>''' encodeLZ78('''string''' s): | ||
| + | '''string''' buffer = "" <font color=green>// текущий префикс</font> | ||
| + | '''map<string, int>''' dict = {} <font color=green>// словарь</font> | ||
| + | '''list<Node>''' ans = [] <font color=green>// ответ</font> | ||
| + | '''for''' i = 0 .. s.length - 1: | ||
| + | '''if''' dict.hasKey(buffer + s[i]): <font color=green>// можем ли мы увеличить префикс</font> | ||
| + | buffer += s[i] | ||
| + | '''else''': | ||
| + | ans.push({dict[buffer], s[i]}) <font color=green>// добавляем пару в ответ</font> | ||
| + | dict[buffer + s[i]] = dict.length + 1 <font color=green>// добавляем слово в словарь</font> | ||
| + | buffer = "" <font color=green>// сбрасываем буфер</font> | ||
| + | '''if''' not (buffer is empty): <font color=green>// если буффер не пуст - этот код уже был, нужно его добавить в конец словаря</font> | ||
| + | last_ch = buffer.peek() <font color=green>// берем последний символ буффера, как "новый" символ</font> | ||
| + | buffer.pop() <font color=green>// удаляем последний символ из буфера</font> | ||
| + | ans.push({dict[buffer], last_ch}) <font color=green>// добавляем пару в ответ</font> | ||
| + | '''return''' ans | ||
| + | </code> | ||
| + | |||
| + | === Пример === | ||
| + | |||
| + | В этот раз для примера возьмем строку <tex>abacababacabc</tex>. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | ! Словарь || Осталось обработать || Найденный префикс || Код || Примечание | ||
| + | |- | ||
| + | | <tex>\varnothing</tex> || <tex>abacababacabc</tex> || {{---}} || <tex>\langle 0, a \rangle</tex> || В словаре ничего не нашлось, вместо номера в словаре указываем <tex>0</tex> | ||
| + | |- | ||
| + | | <tex>a</tex> || <tex>bacababacabc</tex> || {{---}} || <tex>\langle 0, b \rangle</tex> || | ||
| + | |- | ||
| + | | <tex>a</tex>, <tex>b</tex> || <tex>acababacabc</tex> || <tex>a</tex> || <tex>\langle 1, c \rangle</tex> || Нашелся префикс <tex>a</tex> (слова в словаре нумеруются с <tex>1</tex>), добавляем <tex>ac</tex> | ||
| + | |- | ||
| + | | <tex>a</tex>, <tex>b</tex>, <tex>ac</tex> || <tex>ababacabc</tex> || <tex>a</tex> || <tex>\langle 1, b \rangle</tex> || | ||
| + | |- | ||
| + | | <tex>a</tex>, <tex>b</tex>, <tex>ac</tex>, <tex>ab</tex> || <tex>abacabc</tex> || <tex>ab</tex> || <tex>\langle 4, a \rangle</tex> || | ||
| + | |- | ||
| + | | <tex>a</tex>, <tex>b</tex>, <tex>ac</tex>, <tex>ab</tex>, <tex>aba</tex> || <tex>cabc</tex> || {{---}} || <tex>\langle 0, c \rangle </tex> || | ||
| + | |- | ||
| + | | <tex>a</tex>, <tex>b</tex>, <tex>ac</tex>, <tex>ab</tex>, <tex>aba</tex>, <tex>c</tex> || <tex>abc</tex> || <tex>ab</tex> || <tex>\langle 4, c\rangle </tex> || | ||
| + | |} | ||
| + | |||
| + | Результатом кодирования является список пар: <tex>\left[ \langle 0, a \rangle, \langle 0, b \rangle, \langle 1, c \rangle, \langle 1, b \rangle, \langle 4, a \rangle, \langle 0, c \rangle, \langle 4, c \rangle \right]</tex> | ||
| + | |||
| + | === Декодирование === | ||
| + | |||
| + | Декодирование происходит аналогично кодированию, на основе декодируемой информации строим словарь и берем из него значения. | ||
| + | |||
| + | <code> | ||
| + | '''string''' decodeLZ78('''list<Node>''' encoded): | ||
| + | '''list<string>''' dict = [""] <font color=green>// словарь, слово с номером 0 {{---}} пустая строка</font> | ||
| + | '''string''' ans = "" <font color=green>// ответ</font> | ||
| + | '''for''' node '''in''' encoded: | ||
| + | word = dict[node.pos] + node.next <font color=green>// составляем слово из уже известного из словаря и новой буквы</font> | ||
| + | ans += word <font color=green>// приписываем к ответу</font> | ||
| + | dict.push(word) <font color=green>// добавляем в словарь</font> | ||
| + | '''return''' ans | ||
| + | </code> | ||
| + | |||
| + | === Модификации === | ||
| + | Одна из главных проблем <tex>\mathrm{LZ78}</tex> {{---}} размер словаря. Он ничем не ограничен и может достигать огромных размеров на больших объемах входных данных. В различных модификациях установлены лимиты на размер словаря: при достижении определенного лимита словарь может «замораживаться» (в него перестают добавляться новые слова), из словаря могут удалять менее используемые или самые старые слова и так далее. | ||
| + | |||
| + | Самой известной модификацией <tex>\mathrm{LZ78}</tex> является [[Алгоритм LZW|LZW]]. В нём есть две важных особенности. Первая {{---}} словарь изначально инициализирован всеми символами алфавита. Вторая {{---}} после нахождения максимального префикса поиск следующего префикса начинается не с символа после неподходящего, а с самого неподходящего символа. Это позволяет не выводить неподходящий символ, а выяснять его прямо из словаря. Эта модификация используется в том числе в изображениях в формате <tex>\mathrm{GIF}</tex><ref>[https://en.wikipedia.org/wiki/GIF Wikipedia {{---}} GIF]</ref>. | ||
| + | |||
| + | == См. также == | ||
| + | * [[Алгоритм RLE]] | ||
| + | * [[Алгоритм LZW]] | ||
| + | * [[Алгоритм LZSS]] | ||
| + | * [[Алгоритм LZMA]] | ||
| + | |||
| + | == Примечания == | ||
| + | <references/> | ||
| + | |||
| + | == Источники информации == | ||
| + | * [https://en.wikipedia.org/wiki/LZ77_and_LZ78 Wikipedia {{---}} LZ77 and LZ78] | ||
| + | * [http://ru.wikipedia.org/wiki/LZ77 Википедия {{---}} LZ77] | ||
| + | * [https://fenix.tecnico.ulisboa.pt/downloadFile/1407993358859638/Lempel_Ziv_by_Zeeh.pdf The Lempel Ziv Algorithm] | ||
| + | * [http://rain.ifmo.ru/cat/view.php/vis/data-compression/lz-2000 Визуализатор алгоритма LZ78] | ||
| + | * [http://compression.ru/download/articles/rev_univ/semenyuk_2001_econom_encoding.pdf Семенюк В.В. {{---}} Экономное кодирование дискретной информации] | ||
| + | * [http://mf.grsu.by/UchProc/livak/en/po/comprsite/theory_lz77.html Алгоритм LZ77] | ||
| + | * [http://www.binaryessence.com/dct/en000140.htm Lempel-Ziv-78] | ||
| − | + | [[Категория: Дискретная математика и алгоритмы]] | |
| − | + | [[Категория: Алгоритмы сжатия]] | |
Текущая версия на 19:05, 4 сентября 2022
и — алгоритмы сжатия без потерь, опубликованные Абрахамом Лемпелем[1] и Якобом Зивом[2] в и годах соответственно. Эти алгоритмы стали основой других алгоритмов семьи : LZW, LZSS, LZMA и другие. Оба приведенных алгоритма используют словарный подход.
Содержание
LZ77
Идея алгоритма
В кодируемых строках часто содержатся совпадающие длинные подстроки. Идея, лежащая в основе , заключается в замене повторений на ссылки на позиции в тексте, где такие подстроки уже встречались.
Информацию о повторении можно закодировать парой чисел — смещением назад от текущей позиции () и длиной совпадающей подстроки (). В таком случае, например, строка может быть представлена как . Выражение означает «вернись на символов назад и выведи символов».
Алгоритм кодирует ссылки блоками из трёх элементов — . В дополнение к двум уже описанным элементам, новый параметр означает первый символ после найденного совпадающего фрагмента. Если не удалось найти совпадение, то считается, что .
Для эффективного поиска повторов в применяется метод «скользящего окна» — совпадения ищутся не на всём обработанном префиксе, а в небольшом буфере, состоящем из последних обработанных символов. Обычно длина буфера равна , или . Таким образом, при больших объемах ввода алгоритм тратит меньше времени за счет того, что просматривается не вся исходная строка. С другой стороны, слишком маленькая длина словаря может привести к тому, что расположенные далеко друг от друга совпадения (на большем расстоянии, чем длина словаря) не будут учтены, и кодирование станет менее оптимальным.
Также нетривиальным может оказаться то, что при кодировании значение может быть меньше, чем (например, «вернись на символа назад и выведи символов»). Это означает, что подстрока будет повторяться несколько раз так, чтобы каждый символ совпадал с символом, стоящим на позиции раньше, и всего добавилось символов. Например: . Символ всегда можно определить однозначно, даже если он отсутствует в буфере.
Реализация
Хранить результат кодирования будем в списке структур следующего вида:
struct Node:
int offset
int length
char next
Функция findMatching ищет в буфере строку, совпадающую с префиксом суффикса строки , начинающегося с символа и возвращает искомую пару .
Функция shiftBuffer сдвигает буфер вперёд на символов.
// — исходная строка // функция возвращает список блоков list<Node> encodeLZ77(string s): list<Node> ans = [] pos = 0 while pos < s.length: offset, length = findMatching(buffer, pos) // ищем слово в словаре shiftBuffer(length + 1) // перемещаем скользящее окно pos += length ans.push({offset, length, s[pos]}) // добавляем в ответ очередной блок return ans
Пример
Рассмотрим пример кодирования на строке с буфером размера . Квадратом обведен буфер.
| Строка | Совпадение | Закодированная последовательность | Примечание |
|---|---|---|---|
| — | Буфер пуст | ||
| — | В буфере нет | ||
| Здесь выгодно сделать так, что | |||
| Последовательность уже встречалась, но она находится за пределами окна, и её не находит | |||
| Символов в строке больше нет, поэтому в качестве ставим символ конца строки |
Результатом кодирования является список полученных троек: .
Декодирование
Для декодирования необходимо пройти по уже раскодированной строке назад, вывести необходимую последовательность, затем следующий символ.
Псевдокод этого алгоритма:
// — список блоков // функция возвращает декодированную строку string decodeLZ77(list<Node> encoded): ans = "" for node in encoded: if node.length > 0: // если необходим повтор start = ans.length - node.offset // возвращаемся на символов назад for i = 0 .. node.length - 1: // добавляем символов ans += ans[start + i] ans += node.next // добавляем следующий символ return ans
Модификации
Известно много различных модификаций алгоритма . Например, вместо блоков можно хранить отдельно одиночные символы и отдельно — пары (length-distance pair[3]). Алгоритм LZSS является улучшением и хранит однобитный флаг, показывающий, какие данные за ним идут: одиночный символ или пара смещения и длины. В формате [4] на каждую пару смещения и длины выделяется по байта.
Также на основаны алгоритмы [5] и [6] (последний является широко используемым), которые совмещают с алгоритмом Хаффмана, кодируя элементы пар и одиночные символы.
LZ78
Идея алгоритма
Алгоритм имеет немного другую идею: этот алгоритм в явном виде использует словарный подход, генерируя временный словарь во время кодирования и декодирования.
Изначально словарь пуст, а алгоритм пытается закодировать первый символ. На каждой итерации мы пытаемся увеличить кодируемый префикс, пока такой префикс есть в словаре. Кодовые слова такого алгоритма будут состоять из двух частей — номера в словаре самого длинного найденного префикса () и символа, который идет за этим префиксом (). При этом после кодирования такой пары префикс с приписанным символом добавляется в словарь, а алгоритм продолжает кодирование со следующего символа.
Реализация
Будем хранить результат кодирования в такой структуре:
struct Node:
int pos // номер слова в словаре
char next
В качестве словаря будем использовать обычный map:
list<Node> encodeLZ78(string s):
string buffer = "" // текущий префикс
map<string, int> dict = {} // словарь
list<Node> ans = [] // ответ
for i = 0 .. s.length - 1:
if dict.hasKey(buffer + s[i]): // можем ли мы увеличить префикс
buffer += s[i]
else:
ans.push({dict[buffer], s[i]}) // добавляем пару в ответ
dict[buffer + s[i]] = dict.length + 1 // добавляем слово в словарь
buffer = "" // сбрасываем буфер
if not (buffer is empty): // если буффер не пуст - этот код уже был, нужно его добавить в конец словаря
last_ch = buffer.peek() // берем последний символ буффера, как "новый" символ
buffer.pop() // удаляем последний символ из буфера
ans.push({dict[buffer], last_ch}) // добавляем пару в ответ
return ans
Пример
В этот раз для примера возьмем строку .
| Словарь | Осталось обработать | Найденный префикс | Код | Примечание |
|---|---|---|---|---|
| — | В словаре ничего не нашлось, вместо номера в словаре указываем | |||
| — | ||||
| , | Нашелся префикс (слова в словаре нумеруются с ), добавляем | |||
| , , | ||||
| , , , | ||||
| , , , , | — | |||
| , , , , , |
Результатом кодирования является список пар:
Декодирование
Декодирование происходит аналогично кодированию, на основе декодируемой информации строим словарь и берем из него значения.
string decodeLZ78(list<Node> encoded):
list<string> dict = [""] // словарь, слово с номером 0 — пустая строка
string ans = "" // ответ
for node in encoded:
word = dict[node.pos] + node.next // составляем слово из уже известного из словаря и новой буквы
ans += word // приписываем к ответу
dict.push(word) // добавляем в словарь
return ans
Модификации
Одна из главных проблем — размер словаря. Он ничем не ограничен и может достигать огромных размеров на больших объемах входных данных. В различных модификациях установлены лимиты на размер словаря: при достижении определенного лимита словарь может «замораживаться» (в него перестают добавляться новые слова), из словаря могут удалять менее используемые или самые старые слова и так далее.
Самой известной модификацией является LZW. В нём есть две важных особенности. Первая — словарь изначально инициализирован всеми символами алфавита. Вторая — после нахождения максимального префикса поиск следующего префикса начинается не с символа после неподходящего, а с самого неподходящего символа. Это позволяет не выводить неподходящий символ, а выяснять его прямо из словаря. Эта модификация используется в том числе в изображениях в формате [7].
