feat: информация по алгоритмам сжатия без потерь
This commit is contained in:
@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
tags:
|
||||||
|
- конспект
|
||||||
|
- служебное/в_процессе
|
||||||
|
source:
|
||||||
|
- источник не указан
|
||||||
|
---
|
||||||
|
*Уточняю, что конспект - естественный выхлоп моей работы пока я ищу информацию по билету. О полноте речи не идет и приложен он тут только потому что я иногда на эти конспекты ссылаюсь и потому что мне не жалко*
|
||||||
|
|
||||||
|
## 2.5. Графические данные, их представление и кодирование
|
||||||
|
### Классы изображений
|
||||||
|
### Кодирование цвета
|
||||||
|
### Способы и точность кодирования цвета для отображения
|
||||||
|
### Форматы для хранения графических данных
|
||||||
|
### Краткая характеристика наиболее распространенных растровых форматов
|
||||||
|
|
||||||
|
### Алгоритмы сжатия
|
||||||
|
|
||||||
|
Неплохое видео: https://www.youtube.com/watch?v=CJFUN6BrkGE
|
||||||
|
|
||||||
|
*Априори все считается последовательностью чисел. Как правило за единицу данных, которые будут сжиматься, берется 1 байт*
|
||||||
|
#### Групповое кодирование RLE
|
||||||
|
|
||||||
|
![[Pasted image 20241106185944.png]]
|
||||||
|
|
||||||
|
Вместо того, чтобы писать последовательно много раз одинаковое значение, записывается сначала количество повторений байта, а затем сам байт
|
||||||
|
|
||||||
|
То есть если у нас есть последовательность 111112222222227, то мы как бы говорим: "запиши **5** раз **1**, потом **9** раз **2**, потом **1** раз **7**". То есть запишется оно как: "[5]1[9]2[1]7" и если исходное сообщение занимало у нас 15 байт, то новое 6 байт.
|
||||||
|
|
||||||
|
Основная проблема данного алгоритма в том, что число повторений тоже надо хранить и остается хранить их только в сжатом тексте, так что если повторяющихся данных не так много, то алгоритм становится даже вреден. Например: 112431 превратится в [2]1[1]2[1]4[1]3[1]1, и того 10 байт против исходных 6. То есть размер даже увеличился
|
||||||
|
|
||||||
|
То есть алгоритм отлично себя показывает на изображениях, где много областей сплошной одноцветной заливки (как например схема самого алгоритм, приведенная выше)
|
||||||
|
|
||||||
|
### Кодирование по Хаффману (Huffman, 1952)
|
||||||
|
|
||||||
|
Много поработав с естественным языком, мы довольно быстро заметим, что определенные буквы в нем встречаются чаще чем другие. Частотностью символа называется количество этого символа в тексте, к количеству символов в тексте. Проще говоря какой процент всех символов составляет например буква "а"
|
||||||
|
|
||||||
|
Эта особенность языка испокон веков использовалась для взлома простого шифра подстановки [^substitution-cipher], а потому частотность символов была многократно посчитана для почти всех языков, которые имеют письменность. Вот например частотность символов русского языка:
|
||||||
|
|
||||||
|
![[Pasted image 20241106193807.png]]
|
||||||
|
|
||||||
|
Видно, что буква "о" упоминается существенно чаще, чем буквы вроде "ф" или многострадальной "ё", (к которой у русских большая нелюбовь из-за неудобства ее печатного набора).
|
||||||
|
|
||||||
|
Эта особенность, которая несколько веков портила жизнь людям и не давала зашифровать сообщение, можно обернуть и себе на пользу, а именно: логично использовать для шифрования буквы "о" символ с наименьшей длинной, чуть длиннее сделать "е", еще чуть длиннее "а" и так далее вплоть до "ё", которую зашифруем уже как получится.
|
||||||
|
|
||||||
|
Ну и вот перед нами знакомая задачка из ЕГЭ - подобрать коды для символов так, чтобы их можно было однозначно декодировать, а длина сообщения была наименьшей. Здесь же предлагаю вспомнить, что такое прямое условие Фано - ни одно кодовое слово не может быть началом другого кодового слова.
|
||||||
|
|
||||||
|
После этого мы алгоритмически реализуем подбор таких символов.
|
||||||
|
|
||||||
|
**У этого метода есть одна проблема технического характера**: частотности символов для языка в целом и для конкретного файла в частности могут весьма сильно различаться, а мы ведь этот алгоритм не только к текстам хотим применять (У нас-то в голове все с байтами работает). Построить такую таблицу - пол беды, это займет O(n), что вполне допустима даже для гигабайтных данных. А вот как ее при разжимании получить? В классической реализации алгоритма Хаффмана она хранится в начале файла, но она, как вы можете догадаться, сама прибавляет размера нашим данным
|
||||||
|
|
||||||
|
**У этой проблемы есть решение** - на практике мы можем не генерировать таблицу частотностей, а производить кодирование и построение дерева кодирования символов параллельно. Алгоритм Маслаковым не описан, просто знайте что он есть. Если кому интересно, он разбирался в этом [видео](https://youtu.be/CJFUN6BrkGE?si=AX_-n_2Hn1NQAD0k&t=571) (или если ютуб не грузит: https://vk.com/video-209186427_456239058 на 9:41). Этот алгоритм называется "адаптивный метод Хаффмана"
|
||||||
|
|
||||||
|
[^substitution-cipher]: Шифр простой подстановки - шифр, где каждому символу исходного алфавита (например русского), ставится в соответствии символ другого алфавита. Классический пример - замена одних букв на другие: а -> в, г -> а и т.д. И тогда сообщение "Бегите, глупцы" станет "Гжакфж, анхсшэ" (тут поменяны и другие буквы, можете попытаться прикинуть, какой алфавит тут использовался). Символом другого алфавита может быть и просто другой символ. Ярким примером может стать например шифр Стенфорда Пайнса из Гравити Фолз (кстати рекомендую попробовать его расшифровать, мне это доставило некоторое удовольствие)
|
||||||
|
|
||||||
|
### Алгоритм LZW (Lempel-Ziv-Welch - 1984)
|
||||||
|
|
||||||
|
*Весьма сложный для понимания на слух. Алгоритм существует в двух вариантах LZW и LZW78*
|
||||||
|
|
||||||
|
**Идея**: до этого мы кодировали текст уменьшая длинну символов, теперь мы кодируем текст при помощи частей этого самого текста. То есть в слове **abra**cad**abra** есть 2 абсолютно идентичных набора символов. Хотелось бы это как-то учитывать при кодировании
|
||||||
|
|
||||||
|
*Держим в уме, что текст тут только для примера и действительности мы всегда работаем с последовательностями байт*
|
||||||
|
|
||||||
|
#### LZW исходный
|
||||||
|
|
||||||
|
> [!note]+ Скользящее окно
|
||||||
|
> Некий кусок непрерывной памяти из двух частей - словаря, и буффера. Изначально кодируемый текст, насколько помещается, заносится в буффер (представьте буффер справа), а в процессе кодирования вся эта область памяти сдвигается влево (обычным сдвигом << 8)
|
||||||
|
|
||||||
|
**Идея**: кодируемый текст мы помещаем в буффер и в процессе обработки смещаем его в словарь, а внутри словаря потом в процессе кодирования ищем совпадения. Если совпадение нашлось, мы просто указываем в словаре где это совпадение можно отыскать
|
||||||
|
|
||||||
|
**Реализация (в общих чертах)**: загоняем весь текст в буффер, после чего начинаем искать сопадение для первого символа в словаре (в самом начале кодирования совпадений не будет). Пока совпадений нет, сдвигаем всю цепочку на 1 влево (если есть еще символы, которых нет в буфере, попутно доставляем их туда, а все что вылезает за пределы буффера забываем начисто). Как только есть совпадение на 1 символ, пытаемся найти совпадения на 2 символа, потом на 3 и так далее. То есть пытаемся найти как можно более длинное совпадение с содержимым словаря. Если такое есть - даем на него ссылку в закодированном сообщении словаре (в классической реализации - смещение от конца влево и длину совпавшего куска)
|
||||||
|
|
||||||
|
**Проблема:** Если сначала мы ищем совпадение в один символ, потом в 2 символа, потом в 3 и так далее, то оценка сложности покажет сложность алгоритма по времени $O(n!)$. Не очень хорошо. Чем больше будет размер скользящего окна, тем больше потенциальных длинных цепочек мы сможем найти и тем сильнее, соответственно, сжать данные, но тем дольше будет процесс кодирования
|
||||||
|
|
||||||
|
#### LZW78
|
||||||
|
|
||||||
|
*Модифицированная версия, также и проще для понимания*
|
||||||
|
|
||||||
|
У нас есть кодируемый текст и словарь. Поскольку примером данных будет текст, словарь у нас будет представлять обычный массив строк (только придется договориться, что нумерация в нем начинается с единицы а не с нуля).
|
||||||
|
|
||||||
|
**Идея**: мы кодируем текст и попутно составляем словарь. Мы ищем повторяющиеся последовательности символов длиной начиная от нуля и доскольки угодно. Если некоторой последовательности еще нет в словаре, мы ее туда заносим, если есть, мы в выходных данных даем номер совпавшей последовательности
|
||||||
|
|
||||||
|
> [!note]+ Примечание
|
||||||
|
> Искать последовательности мы начинаем с длины 1, что означает, что при кодировании в словарь сначала добавится вхождение в 1 символ. Потом если с этим вхождением в 1 символ случится совпадение, в словарь будет добавлено совпадение уже из двух символов, а если будет совпадение с последовательностью из двух символов, будет добавлена новая последовательность из 3 символов.
|
||||||
|
>
|
||||||
|
> Каждая новая добавленная последовательность создается из совпавшей последовательности + одна буква справа от нее. То есть если "abba" уже есть в словаре, а совпадение случилось в состоянии буфера "**abba**helloworld", то следующей в словарь добавится последовательность "abbah", потому что слева стояла буква "h"
|
||||||
|
|
||||||
|
**Реализация**: теперь вместо символа или последовательности символов мы пишем номер, под которым соответствующая последовательность хранится в словаре (напоминаю, что нумеруем элементы массива в этом примере мы с **единицы**), после чего добавляем следующий за этой последовательностью символ. Если символ в словаре не найден, пишем в качестве числа 0. То есть в "abracadabra" первые 3 символа у нас во время прохода встретятся первый раз и в сжатый текст пойдет 0a0b0r, а вот потом встретится "a", которая есть в словаре, поэтому мы напишем ее порядковый номер, а затем следующий за совпавшей последовательностью символ: 1c, потом в ход пойдет "ad", который закодируется соответственно в 1d и так далее после кодирования получим следующий результат: 0a0b0r1c1d1b3a (заметим, что в данном случае мы ничего не сжали. Это нормально, потому что данные коротковаты, а алгоритмы сжатия дописывают довольно много информации. Порой сжатие не эффективно, тут ничего не поделать)
|
||||||
|
|
||||||
|
**Проблемы**: пусть здесь мы и поправили скорость сжатия (теперь на поиск совпадений уходит $O(n)$), а также мы убрали ограничения на размер скользящего окна и теоретически можем искать совпадения большой длины, можно увидеть, что в этом варианте алгоритма мы не заметили, что "abra" встречается 2 раза без изменений. То есть чтобы алгоритм был эффективен, желательно данные иметь побольше. Также не трудно понять, что если длинных цепочек повторяющихся данных у нас нет, то сжатие пойдет во вред размеру файла и это надо учитывать, поэтому если в файле нечему повторяться, то этот алгоритм не только не эффективен, но подчас и вреден, как мы могли убедиться
|
||||||
|
|
||||||
|
### Сжатие с потерями
|
||||||
Reference in New Issue
Block a user