fix: улучшил объяснения и исправил несколько опечаток

This commit is contained in:
ElectronixTM
2024-11-09 16:46:05 +03:00
parent 63e0ef13e6
commit 0b109f4d81

View File

@ -14,7 +14,7 @@ source:
### Форматы для хранения графических данных ### Форматы для хранения графических данных
### Краткая характеристика наиболее распространенных растровых форматов ### Краткая характеристика наиболее распространенных растровых форматов
### Алгоритмы сжатия ### Алгоритмы сжатия без потерь
Неплохое видео: https://www.youtube.com/watch?v=CJFUN6BrkGE Неплохое видео: https://www.youtube.com/watch?v=CJFUN6BrkGE
@ -41,15 +41,15 @@ source:
Видно, что буква "о" упоминается существенно чаще, чем буквы вроде "ф" или многострадальной "ё", (к которой у русских большая нелюбовь из-за неудобства ее печатного набора). Видно, что буква "о" упоминается существенно чаще, чем буквы вроде "ф" или многострадальной "ё", (к которой у русских большая нелюбовь из-за неудобства ее печатного набора).
Эта особенность, которая несколько веков портила жизнь людям и не давала зашифровать сообщение, можно обернуть и себе на пользу, а именно: логично использовать для шифрования буквы "о" символ с наименьшей длинной, чуть длиннее сделать "е", еще чуть длиннее "а" и так далее вплоть до "ё", которую зашифруем уже как получится. Эта особенность, которая несколько веков портила жизнь людям и не давала зашифровать сообщение, но ее можно обернуть и себе на пользу: при шифровании текста логично использовать для шифрования буквы "о" символ с наименьшей длинной, чуть длиннее сделать "е", еще чуть длиннее "а" и так далее вплоть до "ё", которую зашифруем уже как получится.
Ну и вот перед нами знакомая задачка из ЕГЭ - подобрать коды для символов так, чтобы их можно было однозначно декодировать, а длина сообщения была наименьшей. Здесь же предлагаю вспомнить, что такое прямое условие Фано - ни одно кодовое слово не может быть началом другого кодового слова. Ну и вот перед нами знакомая задачка из ЕГЭ - подобрать коды для символов так, чтобы их можно было однозначно декодировать, а длина сообщения была наименьшей. Здесь же предлагаю вспомнить, что такое прямое условие Фано - ни одно кодовое слово не может быть началом другого кодового слова. Это является условием, чтобы наши сжатые данные можно было однозначно декодировать
После этого мы алгоритмически реализуем подбор таких символов. После этого мы алгоритмически реализуем подбор таких символов.
**У этого метода есть одна проблема технического характера**: частотности символов для языка в целом и для конкретного файла в частности могут весьма сильно различаться, а мы ведь этот алгоритм не только к текстам хотим применять (У нас-то в голове все с байтами работает). Построить такую таблицу - пол беды, это займет O(n), что вполне допустима даже для гигабайтных данных. А вот как ее при разжимании получить? В классической реализации алгоритма Хаффмана она хранится в начале файла, но она, как вы можете догадаться, сама прибавляет размера нашим данным **У этого метода есть одна проблема технического характера**: частотности символов для языка в целом и для конкретного файла в частности могут весьма сильно различаться, а мы ведь этот алгоритм не только к текстам хотим применять (У нас-то в голове все с байтами работает). Построить таблицу частотностей для конкретного файла - пол беды, это займет O(n), что вполне допустима даже для гигабайтных данных. А вот как ее при разжимании получить? В классической реализации алгоритма Хаффмана она хранится в начале файла, но она, как вы можете догадаться, сама прибавляет размера нашим данным.
**У этой проблемы есть решение** - на практике мы можем не генерировать таблицу частотностей, а производить кодирование и построение дерева кодирования символов параллельно. Алгоритм Маслаковым не описан, просто знайте что он есть. Если кому интересно, он разбирался в этом [видео](https://youtu.be/CJFUN6BrkGE?si=AX_-n_2Hn1NQAD0k&t=571) (или если ютуб не грузит: https://vk.com/video-209186427_456239058 на 9:41). Этот алгоритм называется "адаптивный метод Хаффмана" **У этой проблемы есть решение** - на практике мы можем не генерировать таблицу частотностей, а производить кодирование и построение дерева кодирования символов параллельно. Алгоритм Молодяковым не описан, просто знайте что он есть. Если кому интересно, он разбирался в этом [видео](https://youtu.be/CJFUN6BrkGE?si=AX_-n_2Hn1NQAD0k&t=571) (или если ютуб не грузит: https://vk.com/video-209186427_456239058 на 9:41). Этот алгоритм называется "адаптивный метод Хаффмана"
[^substitution-cipher]: Шифр простой подстановки - шифр, где каждому символу исходного алфавита (например русского), ставится в соответствии символ другого алфавита. Классический пример - замена одних букв на другие: а -> в, г -> а и т.д. И тогда сообщение "Бегите, глупцы" станет "Гжакфж, анхсшэ" (тут поменяны и другие буквы, можете попытаться прикинуть, какой алфавит тут использовался). Символом другого алфавита может быть и просто другой символ. Ярким примером может стать например шифр Стенфорда Пайнса из Гравити Фолз (кстати рекомендую попробовать его расшифровать, мне это доставило некоторое удовольствие) [^substitution-cipher]: Шифр простой подстановки - шифр, где каждому символу исходного алфавита (например русского), ставится в соответствии символ другого алфавита. Классический пример - замена одних букв на другие: а -> в, г -> а и т.д. И тогда сообщение "Бегите, глупцы" станет "Гжакфж, анхсшэ" (тут поменяны и другие буквы, можете попытаться прикинуть, какой алфавит тут использовался). Символом другого алфавита может быть и просто другой символ. Ярким примером может стать например шифр Стенфорда Пайнса из Гравити Фолз (кстати рекомендую попробовать его расшифровать, мне это доставило некоторое удовольствие)
@ -68,7 +68,7 @@ source:
**Идея**: кодируемый текст мы помещаем в буффер и в процессе обработки смещаем его в словарь, а внутри словаря потом в процессе кодирования ищем совпадения. Если совпадение нашлось, мы просто указываем в словаре где это совпадение можно отыскать **Идея**: кодируемый текст мы помещаем в буффер и в процессе обработки смещаем его в словарь, а внутри словаря потом в процессе кодирования ищем совпадения. Если совпадение нашлось, мы просто указываем в словаре где это совпадение можно отыскать
**Реализация (в общих чертах)**: загоняем весь текст в буффер, после чего начинаем искать сопадение для первого символа в словаре (в самом начале кодирования совпадений не будет). Пока совпадений нет, сдвигаем всю цепочку на 1 влево (если есть еще символы, которых нет в буфере, попутно доставляем их туда, а все что вылезает за пределы буффера забываем начисто). Как только есть совпадение на 1 символ, пытаемся найти совпадения на 2 символа, потом на 3 и так далее. То есть пытаемся найти как можно более длинное совпадение с содержимым словаря. Если такое есть - даем на него ссылку в закодированном сообщении словаре (в классической реализации - смещение от конца влево и длину совпавшего куска) **Реализация (в общих чертах)**: загоняем весь текст в буффер, после чего начинаем искать сопадение для первого символа в словаре (в самом начале кодирования совпадений не будет). Пока совпадений нет, сдвигаем всю цепочку на 1 влево, если есть еще символы, которых нет в буффере, попутно доставляем их туда *(сдвинулись на 1 влево, в буффере освободилось место под один символ, дописываем в это место следующий в файле символ)*. Словарь и буффер - одна область памяти, называемая скользящим окном (см примечание выше), поэтому при сдвиге влево на один символ, самый левый символ из буффера появится в словаре, вытеснив самый старый символ уже словаря. Вытесненный символ нигде не хранится и просто исчезает из оперативной памяти. Как только есть совпадение на 1 символ, пытаемся найти совпадения на 2 символа, потом на 3 и так далее. То есть пытаемся найти как можно более длинное совпадение с содержимым словаря. Отмечу, что совпадения мы ищем даже тогда, когда словарь пуст. Если такое есть - даем на него ссылку в закодированном сообщении словаре (в классической реализации - смещение от конца влево и длину совпавшего куска)
**Проблема:** Если сначала мы ищем совпадение в один символ, потом в 2 символа, потом в 3 и так далее, то оценка сложности покажет сложность алгоритма по времени $O(n!)$. Не очень хорошо. Чем больше будет размер скользящего окна, тем больше потенциальных длинных цепочек мы сможем найти и тем сильнее, соответственно, сжать данные, но тем дольше будет процесс кодирования **Проблема:** Если сначала мы ищем совпадение в один символ, потом в 2 символа, потом в 3 и так далее, то оценка сложности покажет сложность алгоритма по времени $O(n!)$. Не очень хорошо. Чем больше будет размер скользящего окна, тем больше потенциальных длинных цепочек мы сможем найти и тем сильнее, соответственно, сжать данные, но тем дольше будет процесс кодирования
@ -85,8 +85,22 @@ source:
> >
> Каждая новая добавленная последовательность создается из совпавшей последовательности + одна буква справа от нее. То есть если "abba" уже есть в словаре, а совпадение случилось в состоянии буфера "**abba**helloworld", то следующей в словарь добавится последовательность "abbah", потому что слева стояла буква "h" > Каждая новая добавленная последовательность создается из совпавшей последовательности + одна буква справа от нее. То есть если "abba" уже есть в словаре, а совпадение случилось в состоянии буфера "**abba**helloworld", то следующей в словарь добавится последовательность "abbah", потому что слева стояла буква "h"
**Реализация**: теперь вместо символа или последовательности символов мы пишем номер, под которым соответствующая последовательность хранится в словаре (напоминаю, что нумеруем элементы массива в этом примере мы с **единицы**), после чего добавляем следующий за этой последовательностью символ. Если символ в словаре не найден, пишем в качестве числа 0. То есть в "abracadabra" первые 3 символа у нас во время прохода встретятся первый раз и в сжатый текст пойдет 0a0b0r, а вот потом встретится "a", которая есть в словаре, поэтому мы напишем ее порядковый номер, а затем следующий за совпавшей последовательностью символ: 1c, потом в ход пойдет "ad", который закодируется соответственно в 1d и так далее после кодирования получим следующий результат: 0a0b0r1c1d1b3a (заметим, что в данном случае мы ничего не сжали. Это нормально, потому что данные коротковаты, а алгоритмы сжатия дописывают довольно много информации. Порой сжатие не эффективно, тут ничего не поделать) **Реализация**: теперь вместо символа или последовательности символов мы пишем номер, под которым соответствующая последовательность хранится в словаре (напоминаю, что нумеруем элементы массива в этом примере мы с **единицы**), после чего добавляем следующий за этой последовательностью символ. Если символ в словаре не найден, пишем в качестве числа 0. `{'a', 'b', 'r'}`)*, а вот потом встретится "a", которая есть в словаре, поэтому мы напишем ее порядковый номер, а затем следующий за совпавшей последовательностью символ: 1c (словарь: {'a', 'b', 'r', 'ac'})*, потом в ход пойдет "ad", который закодируется соответственно в 1d и так далее после кодирования получим следующий результат: 0a0b0r1c1d1b3a *(словарь: {'a', 'b', 'r', 'ac', 'ad', 'ab', 'ra'})* (заметим, что в данном случае мы ничего не сжали. Это нормально, потому что данные коротковаты, а алгоритмы сжатия дописывают довольно много информации. Порой сжатие не эффективно, тут ничего не поделать)
**Проблемы**: пусть здесь мы и поправили скорость сжатия (теперь на поиск совпадений уходит $O(n)$), а также мы убрали ограничения на размер скользящего окна и теоретически можем искать совпадения большой длины, можно увидеть, что в этом варианте алгоритма мы не заметили, что "abra" встречается 2 раза без изменений. То есть чтобы алгоритм был эффективен, желательно данные иметь побольше. Также не трудно понять, что если длинных цепочек повторяющихся данных у нас нет, то сжатие пойдет во вред размеру файла и это надо учитывать, поэтому если в файле нечему повторяться, то этот алгоритм не только не эффективен, но подчас и вреден, как мы могли убедиться **Проблемы**: пусть здесь мы и поправили скорость сжатия (теперь на поиск совпадений уходит $O(n)$), а также мы убрали ограничения на размер скользящего окна и теоретически можем искать совпадения большой длины, можно увидеть, что в этом варианте алгоритма мы не заметили, что "abra" встречается 2 раза без изменений. То есть чтобы алгоритм был эффективен, желательно данные иметь побольше. Также не трудно понять, что если длинных цепочек повторяющихся данных у нас нет, то сжатие пойдет во вред размеру файла и это надо учитывать, поэтому если в файле нечему повторяться, то этот алгоритм не только не эффективен, но подчас и вреден, как мы могли убедиться
### Сжатие с потерями ### Алгоритмы сжатия с потерями
*Когда мы говорим о сжатии какого-нибудь текстового документа, потери данных оче ид о н прив дут ни к ч му хор шему, как можете видеть, читать такое не то чтобы очень приятно. Но вот если дело касается изображений - другой разговор. Наш глаз, вообще говоря, штука крайне не точная и вполне может не заметить каких-то деталей. Так что для фото и видео сжатия с потерями - отличный выход (потому что весят эти картиночки они будь здоров)*
### JPEG
JPEG - формат хранения изображения с потерями. Само по себе его обоснование довольно сложно и требует некоторых знаний в области математики. Тем не менее основывается он на некоторых вполне понятных идеях:
1. Если мы сжимаем фотографию или художественное полотно, то как правило 2 соседних пикселя будут близки по цвету
2. Человеческий глаз построен таким образом, что он гораздо восприимчивее к перепадам яркости, чем к изменению цвета (кому интересно, это связано с тем, что в [нашей сетчатке глаза](https://ru.wikipedia.org/wiki/Сетчатка) больше [палочек чем колбочек](https://ru.wikipedia.org/wiki/Фоторецептор)). То есть человеческий глаз намного чувствительнее к изменению яркости нежели к изменению цвета
Процесс сжатия в этом формате довольно длинный. Подробно все шаги пояснять не буду, потому что это ну прям совсем хана