Алгори́тм Ле́мпеля — Зи́ва — Уэлча (Lempel-Ziv-Welch, LZW) — это универсальный алгоритм сжатия данных без потерь, созданный Авраамом Лемпелем, Яаковом Зивом и Терри Велчем. Он был опубликован Велчем в 1984 году[1] в качестве улучшенной реализации алгоритма LZ78, опубликованного Лемпелем и Зивом в 1978 году[2]. Алгоритм разработан так, чтобы его было достаточно просто реализовать как программно, так и аппаратно[1].
Акроним «LZW» указывает на фамилии изобретателей алгоритма: Лемпель, Зив и Велч, но многие[кто?] утверждают, что, поскольку патент принадлежал Зиву, то метод должен называться алгоритмом Зива — Лемпеля — Велча.
Описание
Данный алгоритм при кодировании (сжатии) сообщения динамически создаёт словарь фраз: определённым последовательностям символов (фразам) ставятся в соответствие группы битов (коды) фиксированной длины (например, 12-битные, как предлагается в исходной статье Велча[1]). Словарь инициализируется всеми 1-символьными фразами (в случае 8-битных символов — это 256 фраз). По мере кодирования алгоритм просматривает текст символ за символом слева направо. При чтении алгоритмом очередного символа в данной позиции находится строка W максимальной длины, совпадающая с какой-то фразой из словаря. Затем код этой фразы подаётся на выход, а строка WK, где K — это символ, следующий за W во входном сообщении, вносится в словарь в качестве новой фразы и ей присваивается какой-то код (так как W выбрана жадно, WK ещё не содержится в словаре). Символ K используется в качестве начала следующей фразы. Более формально данный алгоритм можно описать следующей последовательностью шагов:
- Инициализация словаря всеми возможными односимвольными фразами. Инициализация входной фразы W первым символом сообщения.
- Если КОНЕЦ_СООБЩЕНИЯ, то выдать код для W и завершить алгоритм.
- Считать очередной символ K из кодируемого сообщения.
- Если фраза WK уже есть в словаре, то присвоить входной фразе W значение WK и перейти к Шагу 2.
- Иначе выдать код W, добавить WK в словарь, присвоить входной фразе W значение K и перейти к Шагу 2.
Алгоритму декодирования на входе требуется только закодированный текст: соответствующий словарь фраз легко воссоздаётся посредством имитации работы алгоритма кодирования (но есть неочевидные нюансы, обсуждаемые в примере ниже).
Реализация
Примечательной особенностью алгоритма LZW является простота реализации, благодаря которой он до сих пор очень популярен, несмотря на зачастую худшую степень сжатия по сравнению с такими аналогами, как LZ77[3]. Обычно LZW реализуется с помощью префиксного дерева, содержащего фразы из словаря: для нахождения W нужно просто прочитать как можно более длинную строку из корня дерева, затем для добавления новой фразы WK нужно присоединить к найденному узлу нового сына по символу K, а кодом фразы W может выступать индекс узла в массиве, содержащем все узлы.
Кодирование фраз
Использование для фраз кодов фиксированной длины (12 битов в описании Велча[1]) может негативно сказаться на эффективности сжатия, так как, во-первых, для начальных кодируемых символов этот подход скорее будет раздувать данные, а не сжимать (если символ занимает 8 битов), и во-вторых, общий размер словаря (212=4096) получается не так велик. Первая проблема решается кодированием выходной последовательности алгоритмом Хаффмана (возможно, адаптивным) или арифметическим кодированием. Для решения второй используют другие подходы.
Первый простой вариант — применить какой-нибудь оптимальный универсальный код типа кода Левенштейна или кода Элиаса. В таком случае теоретически словарь может расти неограниченно.
Другой более распространённый вариант — изменять максимальный возможный размер словаря с ростом числа фраз.[4] Изначально, например, максимальный размер словаря полагается 29 (28 кодов при этом уже заняты фразами для кодирования 8-битовых одиночных символов) и на код фразы отводится 9 битов. Когда число фраз становится 29, максимальный размер становится 210 и на коды отводится 10 битов. И так далее. Таким образом, теоретически словарь может быть сколь угодно большим. Этот метод продемонстрирован в примере ниже (обычно, тем не менее, максимальный размер словаря ограничивается; например в LZC — популярной модификации LZW, рассматриваемой ниже — длины кодов растут от 9 до 16 битов.).
В большинстве реализаций последнего метода число битов, выделяемых на код фразы, увеличивается до добавления новой фразы WK, переполняющей словарь, но после записи на выход кода W. Например, пусть в данный момент в процессе работы алгоритма длина кода — p битов, и алгоритм собирается выдать код фразы W и добавить новую фразу WK в словарь; если код WK равен 2p (то есть WK переполняет словарь), то сначала выдаётся p-битовый код W и только после этого p увеличивается на один, чтобы последующие коды занимали p+1 битов. Среди ранних реализаций LZW существуют такие, которые увеличивают p до выдачи кода W, то есть код W, выдаваемый перед добавлением WK в словарь, уже занимает p+1 битов (что не является необходимым, так как код W меньше 2p). Такое поведение называется «ранним изменением» (early change). Эта путаница в реализациях побудила Adobe поддерживать оба варианта LZW в PDF (используются ли «ранние изменения», указывается с помощью специального флага в заголовке сжимаемых данных).[5]
Переполнение словаря
Так как коды в алгоритме LZW имеют фиксированную длину, размер словаря ограничен (при использовании кодов нефиксированной длины размер словаря ограничен объёмом доступной памяти). Возникает вопрос: что делать в случае переполнения словаря? Используют несколько стратегий:
- Самый очевидный вариант — просто использовать построенный словарь без дальнейших модификаций.[1] Ясно, что часто это плохая стратегия.
- Авторы некогда популярной утилиты compress[англ.] просто используют построенный словарь, пока степень сжатия остаётся приемлемой, а затем очищают его в случае ухудшения качества сжатия. Такая модификация LZW называется LZC (Lempel-Ziv-Compress, см. ниже).[6]
- П. Тисчер предложил перед вставкой в переполненный словарь новой фразы на очередном шаге алгоритма удалять из словаря фразу, которая дольше всего не использовалась (LRU, Least Recently Used). Такая модификация иногда называется LZT (Lempel-Ziv-Tischer, см. ниже).[7]
Пример
Данный пример показывает алгоритм LZW в действии, показывая состояние выходных данных и словаря на каждой стадии, как при кодировании, так и при раскодировании сообщения. С тем чтобы сделать изложение проще, мы ограничимся простым алфавитом — только заглавные буквы, без знаков препинания и пробелов. Сообщение, которое нужно сжать, выглядит следующим образом:
TOBEORNOTTOBEORTOBEORNOT#
Маркер # используется для обозначения конца сообщения. Тем самым, в нашем алфавите 27 символов (26 заглавных букв от A до Z и #). Компьютер представляет это в виде групп бит, для представления каждого символа алфавита нам достаточно группы из 5 бит на символ. По мере роста словаря, размер групп должен расти, с тем чтобы учесть новые элементы. 5-битные группы дают 25 = 32 возможных комбинации бит, поэтому, когда в словаре появится 33-е слово, алгоритм должен перейти к 6-битным группам. Заметим, что, поскольку используется группа из всех нулей 00000, то 33-я группа имеет код 32. Начальный словарь будет содержать:
# = 00000 A = 00001 B = 00010 C = 00011 . . . Z = 11010
Кодирование
Без использования алгоритма LZW, при передаче сообщения, как оно есть — 25 символов по 5 бит на каждый — оно займёт 125 бит. Сравним это с тем, что получается при использовании LZW:
Символ: Битовый код: Новая запись словаря: (на выходе) T 20 = 10100 O 15 = 01111 27: TO B 2 = 00010 28: OB E 5 = 00101 29: BE O 15 = 01111 30: EO R 18 = 10010 31: OR <--- со следующего символа начинаем использовать 6-битные группы N 14 = 01110 32: RN O 15 = 01111 33: NO T 20 = 10100 34: OT TO 27 = 11011 35: TT BE 29 = 11101 36: TOB OR 31 = 11111 37: BEO TOB 36 = 100100 38: ORT EO 30 = 11110 39: TOBE RN 32 = 100000 40: EOR OT 34 = 100010 41: RNO # 0 = 00000 42: OT# Общая длина = 13*5 + 3*6 = 83 бит.
Таким образом, используя LZW, мы сократили сообщение на 29 бит из 125 — это почти 22 %. Если сообщение будет длиннее, то элементы словаря будут представлять всё более и более длинные части текста, благодаря чему повторяющиеся слова будут представлены очень компактно.
Декодирование
Теперь представим, что мы получили закодированное сообщение, приведённое выше, и нам нужно его декодировать. Прежде всего, нам нужно знать начальный словарь, а последующие записи словаря мы можем реконструировать уже на ходу, поскольку они являются просто конкатенацией предыдущих записей.
Данные: На выходе: Новая запись: Полная: Частичная: 10100 = 20 T 27: T? 01111 = 15 O 27: TO 28: O? 00010 = 2 B 28: OB 29: B? 00101 = 5 E 29: BE 30: E? 01111 = 15 O 30: EO 31: O? 10010 = 18 R 31: OR 32: R? <---- начинаем использовать 6-битные группы 01110 = 14 N 32: RN 33: N? 01111 = 15 O 33: NO 34: O? 10100 = 20 T 34: OT 35: T? 11011 = 27 TO 35: TT 36: TO? <---- для 37, добавляем только первый элемент 11101 = 29 BE 36: TOB 37: BE? следующего слова словаря 11111 = 31 OR 37: BEO 38: OR? 100100 = 36 TOB 38: ORT 39: TOB? 11110 = 30 EO 39: TOBE 40: EO? 100000 = 32 RN 40: EOR 41: RN? 100010 = 34 OT 41: RNO 42: OT? 00000 = 0 #
Единственная небольшая трудность может возникнуть, если новое слово словаря пересылается немедленно. В приведённом выше примере декодирования, когда декодер встречает первый символ, T, он знает, что слово 27 начинается с T, но чем оно заканчивается? Проиллюстрируем проблему следующим примером. Мы декодируем сообщение ABABA:
Данные: На выходе: Новая запись: Полная: Частичная: . . . 011101 = 29 AB 46: (word) 47: AB? 101111 = 47 AB? <--- что нам с этим делать?
На первый взгляд, для декодера это неразрешимая ситуация. Мы знаем наперёд, что словом 47 должно быть ABA, но как декодер узнает об этом? Заметим, что слово 47 состоит из слова 29 плюс символ, идущий следующим. Таким образом, слово 47 заканчивается на «символ, идущий следующим». Но, поскольку это слово посылается немедленно, то оно должно начинаться с «символа, идущего следующим», и поэтому оно заканчивается тем же символом, что и начинается, в данном случае — A. Этот трюк позволяет декодеру определить, что слово 47 — это ABA.
В общем случае, такая ситуация появляется, когда кодируется последовательность вида cScSc, где c — это один символ, а S — строка, причём слово cS уже есть в словаре.
Теоретическая оценка эффективности
Теоретические свойства LZW с неограниченным словарём в целом такие же, как и у LZ78, и анализ этих двух методов сжатия аналогичен. При рассмотрении неограниченного словаря естественно предполагать, что выходные фразы кодируются кодами переменной длины, например, каким-нибудь универсальным кодом или фиксированным кодом, размер которого растёт вместе с максимальным размером словаря (см. раздел о реализации).
Асимптотическая оптимальность
Для теоретической оценки эффективности данный метод сжатия сравнивают с некоторой эталонной метрикой. К сожалению, идеальная эталонная метрика сжатия данных — Колмогоровская сложность — невычислима даже приблизительно, и поэтому любой алгоритм сжатия заведомо проигрывает в сравнении с ней. Лемпель и Зив предложили ослабленную версию Колмогоровской сложности — сжатие конечными автоматами[2]. По техническим причинам данная метрика определяется для бесконечных строк. Зафиксируем конечный алфавит . Пусть дана бесконечная строка над . Обозначим через число битов в кодировке LZW с неограниченным словарём для строки . Отсюда имеем:
где — это число фраз в LZW кодировке для . Зив показал[8], что
где — это метрика сжатия конечными автоматами, определённая далее[2]. Таким образом, коэффициент сжатия LZW (отношение к — числу битов, занимаемых несжатой строкой) асимптотически ограничен и в этом смысле LZW оптимален. Более того, если размер словаря ограничен и при переполнении используется стратегия удаления наименее используемых фраз, LZW также асимптотически оптимален в следующем смысле:[8]
где обозначает число битов в кодировке LZW со словарём, в котором содержатся не более фраз, каждая фраза имеет длину не больше и при переполнении словаря выбрасывается реже всего использовавшаяся фраза (этот вариант похож на LZT; см. модификации). Отметим, что аналогичными свойствами обладают алгоритмы LZ78 и LZ77 (включая аналогичные варианты, соответственно, со словарём и скользящим окном ограниченного размера)[8]. Определим теперь .
Метрика во многом аналогична Колмогоровской сложности, но вместо полноценных машин Тьюринга в её определении используются машины Тьюринга с конечной памятью, то есть конечные автоматы. Для данного числа обозначим через множество всех детерминированных автоматов Мили которые имеют состояний и перекодируют строки над алфавитом в бинарные последовательности, так что по выходу автомата можно восстановить исходную строку; более точно, на рёбрах автомата записаны бинарные строки (возможно пустые), так что по любой конечной строке над алфавитом автомат приходит в некоторое состояние и в процессе выдают бинарную последовательность , и по последовательности и состоянию можно однозначно восстановить (чтобы сжимающий автомат мог иметь пустые строки на рёбрах, строка восстанавливается не только по последовательности , но и по конечному состоянию [9]). Для данной бесконечной строки определим:[8]
где обозначает число битов в . Таким образом, представляет собой оценку на наименьший возможный коэффициент сжатия, которого можно достигнуть при сжатии строки конечными автоматами. Заметим, что из-за неограниченности словаря алгоритм LZW не может быть смоделирован с помощью автомата Мили, в отличие от алгоритма LZW с ограниченным словарём с не более чем фразами длины не более (размер такого автомата Мили, естественно, зависит от ).
Неоптимальность числа фраз
Метрика сжатия конечными автоматами, хоть и является естественной, не так эффективна, как может показаться на первый взгляд. Так, асимптотически оптимальным по отношению к является любой алгоритм сжатия, который разбивает данную строку на различные фразы (то есть при ) и затем кодирует , используя битов[9]. Ясно, что алгоритм, удовлетворяющий таким слабым критериям, вовсе не обязан быть эффективным на практике. Вдобавок, метрика сжатия конечными автоматами не отражает способности многих методов сжатия заменять длинные повторяющиеся фрагменты в данных ссылками на место, где такой фрагмент встретился впервые (у конечного автомата просто не достанет памяти для сравнения длинных фрагментов). Именно этот механизм зачастую является причиной эффективности сжатия больших объёмов данных на практике, и как показано далее, LZW (и LZ78) достаточно плохо справляются с данной задачей в худшем случае по сравнению, например, с LZ77.
Алгоритм LZW с неограниченным размером словаря разлагает данную конечную строку на фразы , так что каждая фраза либо является символом, либо равна , где — какое-то число меньшее , а — это первый символ фразы . Существуют и другие разложения вида для строки и естественно возникает вопрос: насколько хорошо разложение, построенное жадным алгоритмом LZW?
Пусть обозначает минимальное число, такое что строка представима в виде разложения , в котором каждая строка либо является символом, либо равна , где — какое-то число меньшее , а — первый символ строки . Сержио Де Агостино и Рикардо Сильвестри[10] доказали, что в худшем случае может быть больше в раза, даже если алфавит содержит всего лишь два символа (они также показали, что то есть данная оценка оптимальна). Иными словами, жадная стратегия даёт в данном случае результаты очень далёкие от оптимальных. Частично оправдывает подход LZW то, что построение оптимального разложения с фразами является NP-полной задачей, как показали Де Агостино и Сторер[11].
Другой естественный вопрос: насколько хорош LZW по сравнению с LZ77? Известно, что LZ77 жадным образом разлагает строку на фразы , так что каждая фраза либо является символом, либо является подстрокой строки . Хуке, Лори, Ре[12] и Чарикар и др.[13] показали, что в худшем случае может быть в раз больше, чем . С другой стороны, известно, что всегда не меньше , и даже более того, всегда не меньше .[13] Иными словами, LZW и даже «оптимальная» версия LZW, содержащая фраз, не может быть лучше LZ77 (по крайней мере существенно — обратите внимание, что здесь обсуждается число фраз, а не способ их кодирования), а в некоторых патологических случаях может быть катастрофически хуже.
Применение
На момент своего появления алгоритм LZW давал лучший коэффициент сжатия для большинства приложений, чем любой другой хорошо известный метод того времени. Он стал первым широко используемым на компьютерах методом сжатия данных.
Алгоритм (а точнее его модификация, LZC, см. ниже) был реализован в программе compress[англ.], которая стала более или менее стандартной утилитой Unix-систем приблизительно в 1986 году. Несколько других популярных утилит-архиваторов также используют этот метод или близкие к нему.
В 1987 году алгоритм стал частью стандарта на формат изображений GIF. Он также может (опционально) использоваться в формате TIFF. Помимо этого, алгоритм применяется в протоколе модемной связи v.42bis и стандарте PDF[14] (хотя по умолчанию большая часть текстовых и визуальных данных в PDF сжимается с помощью алгоритма Deflate).
Патенты
На алгоритм LZW и его вариации был выдан ряд патентов, как в США, так и в других странах. На LZ78 был выдан американский патент U.S. Patent 4 464 650, принадлежащий Sperry Corporation, позднее ставшей частью Unisys Corporation. На LZW в США были выданы два патента: U.S. Patent 4 814 746, принадлежащий IBM, и патент Велча U.S. Patent 4 558 302 (выдан 20 июня 1983 года), принадлежащий Sperry Corporation, позднее перешедший к Unisys Corporation. К настоящему времени сроки всех патентов истекли.
Unisys, GIF и PNG
При разработке формата GIF в CompuServe не знали о существовании патента U.S. Patent 4 558 302. В декабре 1994 года, когда в Unisys стало известно об использовании LZW в широко используемом графическом формате, эта компания распространила информацию о своих планах по взысканию лицензионных отчислений с коммерческих программ, имеющих возможность по созданию GIF-файлов. В то время формат был уже настолько широко распространён, что большинство компаний — производителей ПО не имело другого выхода, кроме как заплатить. Эта ситуация стала одной из причин разработки графического формата PNG (неофициальная расшифровка: «PNG’s Not GIF»[15]). В конце августа 1999 года Unisys прервала действие безвозмездных лицензий на LZW для бесплатного и некоммерческого ПО[16], а также для пользователей нелицензированных программ, понудив League for Programming Freedom развернуть кампанию «сожжём все GIF’ы»[17] и информировать публику об имеющихся альтернативах. Многие эксперты в области патентного права отмечали, что патент не распространяется на устройства, которые могут лишь разжимать LZW-данные, но не сжимать их; по этой причине популярная утилита gzip может читать .Z-файлы, но не записывать их.
20 июня 2003 года истёк срок оригинального американского патента, что означает, что Unisys не может больше собирать по нему лицензионные отчисления. Аналогичные патенты в Европе, Японии и Канаде истекли в 2004 году.
Модификации
- LZC (1985, Thomas S. W., McKie J., Davies S., Turkowski K., Woods J. A. и Orost J. W.)[6] — одна из наиболее известных практических реализаций LZW, представленная в утилите compress[англ.] (Lempel-Ziv-Compress). LZC использует от 9 до 16 битов для кодов фраз (число битов растёт с ростом размера словаря, см. раздел о реализации); когда словарь переполняется, LZC просто продолжает его использование без дальнейшей модификации, но при этом алгоритм следит за степенью сжатия данных и, когда сжатие с текущим словарём становится неприемлемо плохим, LZC очищает словарь и продолжает работу. LZC сжимает хуже, чем LZW, но зато скорость сжатия выше.
- LZT (1985, Tischer P.)[7] — другой известный вариант LZW. При переполнении словаря LZT продолжает работу, но каждый раз перед вставкой новой фразы в словарь, он удаляет фразу, которая соответствует дольше всего не используемому листу в префиксном дереве, содержащем все фразы. В реализации LZT все листья помещаются в связный список, и каждый раз после использования в ходе сжатия листья перемещаются в голову списка; таким образом, листья, которые не использовались дольше всего, находятся в хвосте списка (дальнейшие детали очевидны).
- LZMW (1985, Виктор Миллер и Марк Вегман[англ.])[18] — находит во входном потоке самую длинную строку, которая уже имеется в словаре (обозначим её W), выдаёт на выход код W, а затем добавляет в словарь строку VW, где V — это строка из словаря, код которой был выдан на предыдущем шаге (изначально V является пустой строкой). Таким образом, строки в словаре растут быстрее, чем в исходном варианте LZW, и на практике часто удаётся получить лучшее качество сжатия. Миллер и Вегман также предложили в случае недостатка памяти удалять из словаря фразы, которые редко используются, так же как это делается в LZT.
- LZAP (1988, Джеймс Сторер)[19] — модификация LZMW, отличающаяся тем, что на каждом шаге в словарь добавляется не только строка VW, но и строки VW1, VW2, …, VW|W|-1, где Wi — это префикс W длины i. («AP» в «LZAP» означает «all prefixes», то есть «все префиксы».) Например, если V = wiki и W = pedia — то есть на предыдущем шаге алгоритм нашёл в словаре строку wiki, а на данном нашёл pedia — то алгоритм добавит в словарь строку wikipedia, как сделал бы LZMW, но также добавит строки wikip, wikipe, wikiped, wikipedi. Алгоритм LZAP проще для реализации, чем LZMW, но в то же время он сохраняет большинство преимуществ LZMW за исключением того, что коды словарных строк занимают больше места, потому что словарь более раздут.
См. также
Примечания
Литература
Wikiwand in your browser!
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.