Главная
Архив новостей
Безопасность в Unix
Безопасность в Windows
Сборник FAQ'ов
Телефония, фрикинг
Кредитные карты
Криптография
Истории о хакерах
Программы, утилиты
_el@sp.sz.ru

RB2 Network

Hекотоpые методы сжатия данных


N.B.  Здесь рассматриваются только алгоритмы производящие сжатие без потерь,
      т.е. допускающие восстановление исходной информации "байт в байт".

 Running -  Это самый простой из  методов упаковки информации . Предположите
            что Вы имеете строку текста, и в конце строки стоит 40 пробелов.
алицо явная избыточность имеющейся информации.  Проблема сжатия этой строки
решается  очень просто - эти  40  пробелов ( 40 байт ) сжимаются в 3 байта с
помощью упаковки их по методу повторяющихся символов (running). Первый байт,
стоящий  вместо  40  пробелов в  сжатой  строке , фактически  будет  явлться
пробелом ( последовательность была из пробелов ) . Второй байт - специальный
байт "флажка" который указывает что мы должны развернуть предыдущий в строке
байт в  последовательность  при  восстановлении  строки . Третий байт - байт
счета ( в нашем случае это будет 40 ). Как Вы сами можете видеть, достаточно
чтобы любой раз, когда мы имеем последовательность  из  более 3-х одинаковых
символов, заменять их выше  описанной  последовательностью , чтобы на выходе
получить блок информации меньший по размеру, но  допускающий  восстановление
информации в исходном виде.
      Оставляя все сказанное выше истинным , добавлю  лишь то, что в  данном
методе основной проблемой является выбор того самого байта "флажка", так как
в реальных  блоках  информации  как  правило  используются все 256 вариантов
байта и нет возможности иметь 257 вариант - "флажок". а  первый  взгляд эта
проблема кажется неразрешимой , но к  ней  есть ключик , который  Вы найдете
прочитав о кодировании с помощью алгоритма Хаффмана ( Huffman ).


 LZW   -   История  этого алгоритма начинается с опубликования в мае 1977 г.
           Дж. Зивом ( J. Ziv ) и А. Лемпелем ( A. Lempel ) статьи в журнале
" Информационные теории " под названием " IEEE  Trans ". В  последствии этот
алгоритм  был доработан Терри А. Велчем ( Terry A. Welch ) и в окончательном
варианте отражен в статье " IEEE Compute " в июне 1984 . В этой статье  опи-
сывались  подробности алгоритма и некоторые  общие проблемы с которыми можно
столкнуться при его реализации. Позже этот алгоритм  получил  название - LZW
( Lempel - Ziv - Welch ) .
    Алгоритм LZW представляет собой алгоритм кодирования последовательностей
неодинаковых символов. Возьмем для примера строку " Объект TSortedCollection
порожден от TCollection.".  Анализируя эту строку мы можем видеть, что слово
"Collection"  повторяется  дважды. В этом слове 10 символов - 80 бит. И если
мы сможем заменить это слово  в  выходном файле, во втором его включении, на
ссылку на первое включение, то получим сжатие информации. Если рассматривать
входной блок информации размером не более 64К и ограничится длинной кодируе-
мой строки в 256 символов, то учитывая байт "флаг" получим, что строка из 80
бит заменяется  8+16+8 = 32 бита. Алгоритм LZW как-бы "обучается" в процессе
сжатия файла. Если  существуют  повторяющиеся  строки в файле , то они будут
закодированны в таблицу. Очевидным  преимуществом алгоритма является то, что
нет  необходимости  включать  таблицу кодировки в сжатый файл. Другой важной
особенностью является то, что сжатие по алгоритму LZW является однопроходной
операцией  в  противоположность  алгоритму  Хаффмана ( Huffman ) ,  которому
требуется два прохода.


 Huffman - Сначала кажется что создание файла меньших  размеров из исходного
           без  кодировки  последовательностей или исключения повтора байтов
будет  невозможной  задачей. о  давайте  мы заставим себя сделать несколько
умственных  усилий  и  понять  алгоритм Хаффмана ( Huffman ). Потеряв не так
много времени мы приобретем знания и дополнительное место на дисках.
      Сжимая  файл  по алгоритму Хаффмана первое что мы должны сделать - это
необходимо  прочитать  файл  полностью  и подсчитать сколько раз встречается
каждый  символ  из  расширенного  набора  ASCII. Если мы будем учитывать все
256 символов, то  для  нас не будет разницы в сжатии текстового и EXE файла.
После  подсчета  частоты  вхождения  каждого символа, необходимо просмотреть
таблицу  кодов  ASCII  и  сформировать  мнимую  компоновку  между  кодами по
убыванию. То  есть  не  меняя  местонахождение  каждого символа из таблицы в
памяти  отсортировать  таблицу  ссылок  на них по убыванию. Каждую ссылку из
последней таблицы назовем "узлом".  В дальнейшем ( в дереве ) мы будем позже
размещать  указатели  которые  будут  указывает  на этот "узел". Для ясности
давайте рассмотрим пример:

      Мы  имеем  файл  длинной  в  100 байт и имеющий 6 различных символов в
себе . Мы  подсчитали  вхождение  каждого  из  символов  в  файл  и получили
следующее :

         LLLLLLLLLLLLLLLLLгLLLLLгLLLLLгLLLLLгLLLLLгLLLLLгLLLLL©
        Ё     cимвол      Ё  A  Ё  B  Ё  C  Ё  D  Ё  E  Ё  F  Ё
        ¬LLLLLLLLLLLLLLLLL-LLLLL-LLLLL-LLLLL-LLLLL-LLLLL-LLLLL|
        Ё число вхождений Ё  10 Ё  20 Ё  30 Ё  5  Ё  25 Ё  10 Ё
        -LLLLLLLLLLLLLLLLL|LLLLL|LLLLL|LLLLL|LLLLL|LLLLL|LLLLL>

     Теперь  мы  берем  эти  числа  и будем называть их частотой вхождения
для каждого символа. Разместим таблицу как ниже.

         LLLLLLLLLLLLLLLLLгLLLLLгLLLLLгLLLLLгLLLLLгLLLLLгLLLLL©
        Ё     cимвол      Ё  C  Ё  E  Ё  B  Ё  F  Ё  A  Ё  D  Ё
        ¬LLLLLLLLLLLLLLLLL-LLLLL-LLLLL-LLLLL-LLLLL-LLLLL-LLLLL|
        Ё число вхождений Ё  30 Ё  25 Ё  20 Ё  10 Ё  10 Ё  5  Ё
        -LLLLLLLLLLLLLLLLL|LLLLL|LLLLL|LLLLL|LLLLL|LLLLL|LLLLL>

     Мы возьмем из последней таблицы  символы с наименьшей частотой. В нашем
случае  это  D (5) и какой либо символ из F или A (10), можно взять любой из
них например A.

    Сформируем из "узлов" D и A новый "узел", частота вхождения для которого
будет равна сумме частот D и A :

   Частота         30    10     5     10     20     25
   Символа          C     A     D      F      B      E
                          Ё     Ё
                          -LLгLL>
                             |L©
                            Ё15Ё  = 5 + 10
                            -LL>
     омер  в  рамке - сумма частот символов D и A. Теперь мы снова ищем два
символа с самыми  низкими частотами вхождения. Исключая из просмотра D и A и
рассматривая  вместо  них новый "узел" с суммарной частотой вхождения. Самая
низкая  частота  теперь  у F и нового "узла". Снова сделаем операцию слияния
узлов :

   Частота         30    10     5     10     20     25
   Символа          C     A     D      F      B      E
                          Ё     Ё      Ё
                          Ё     Ё      Ё
                          Ё  LL©Ё      Ё
                          -L|15¬>      Ё
                            -гL>       Ё
                             Ё         Ё
                             Ё     LL© Ё
                             -LLLL|25¬L> = 10 + 15
                                  -LL>
     Рассматриваем таблицу снова для следующих двух символов ( B и E ).
Мы продолжаем в этот режим пока все "дерево" не сформировано, т.е. пока все
не сведется к одному узлу.

   Частота         30    10     5     10     20     25
   Символа          C     A     D      F      B      E
                    Ё     Ё     Ё      Ё      Ё      Ё
                    Ё     Ё     Ё      Ё      Ё      Ё
                    Ё     Ё  LL©Ё      Ё      Ё      Ё
                    Ё     -L|15¬>      Ё      Ё      Ё
                    Ё       -гL>       Ё      Ё      Ё
                    Ё        Ё         Ё      Ё      Ё
                    Ё        Ё     LL© Ё      Ё  LL© Ё
                    Ё        -LLLL|25¬L>      -L|45¬L>
                    Ё             -гL>          -гL>
                    Ё     LL©      Ё             Ё
                    -LLLL|55¬LLLLLL>             Ё
                         -Lг>                    Ё
                           Ё    LLLLLLLLLLLL©    Ё
                           -LLL| Root (100) ¬LLLL>
                               -LLLLLLLLLLLL>

     Теперь когда наше дерево создано, мы можем кодировать файл . Мы должны
всенда начнинать  из корня ( Root ) . Кодируя первый символ (лист дерева С)
Мы прослеживаем  вверх по дереву все повороты ветвей и если мы делаем левый
поворот, то запоминаем 0-й бит, и аналогично 1-й бит  для правого поворота.
Так для C, мы будем идти влево к 55 ( и запомним 0 ), затем снова влево (0)
к самому  символу . Код  Хаффмана для нашего символа C - 00. Для следующего
символа ( А )  у  нас  получается - лево,право,лево,лево , что выливается в
последовательность 0100. Выполнив  выше сказанное для всех символов получим

   C = 00   ( 2 бита )
   A = 0100 ( 4 бита )
   D = 0101 ( 4 бита )
   F = 011  ( 3 бита )
   B = 10   ( 2 бита )
   E = 11   ( 2 бита )

     Каждый символ изначально представлялся 8-ю битами ( один байт ), и так
как мы уменьшили число битов необходимых для представления каждого символа,
мы следовательно  уменьшили  размер  выходного  файла . Сжатие  складывется
следующим образом :

        LLLLLLLLLLгLLLLLLLLLLLLLLLLгLLLLLLLLLLLLLLLLLLLгLLLLLLLLLLLLLL©
       Ё Частота  Ё  первоначально Ё  уплотненные биты Ё уменьшено на Ё
       ¬LLLLLLLLLL-LLLLLLLLLLLLLLLL-LLLLLLLLLLLLLLLLLLL-LLLLLLLLLLLLLL|
       Ё  C 30    Ё  30 x 8 = 240  Ё    30 x 2 = 60    Ё      180     Ё
       Ё  A 10    Ё  10 x 8 =  80  Ё    10 x 3 = 30    Ё       50     Ё
       Ё  D 5     Ё   5 x 8 =  40  Ё     5 x 4 = 20    Ё       20     Ё
       Ё  F 10    Ё  10 x 8 =  80  Ё    10 x 4 = 40    Ё       40     Ё
       Ё  B 20    Ё  20 x 8 = 160  Ё    20 x 2 = 40    Ё      120     Ё
       Ё  E 25    Ё  25 x 8 = 200  Ё    25 x 2 = 50    Ё      150     Ё
       -LLLLLLLLLL|LLLLLLLLLLLLLLLL|LLLLLLLLLLLLLLLLLLL|LLLLLLLLLLLLLL>

     Первоначальный размер файла : 100 байт - 800 бит;
            Размер сжатого файла :  30 байт - 240 бит;

       240 - 30% из 800 , так что мы сжали этот файл на 70%.

    Все  это довольно хорошо, но неприятность находится в том факте, что для
восстановления первоначального файла, мы  должны  иметь декодирующее дерево,
так как деревья будут различны для разных файлов .  Следовательно мы  должны
сохранять  дерево  вместе  с  файлом . Это превращается в итоге в увеличение
размеров выходного файла .
    В  нашей  методике  сжатия  и  каждом  узле находятся 4 байта указателя,
по этому, полная таблица для 256 байт будет приблизительно 1 Кбайт  длинной.
    Таблица в нашем  примере  имеет  5 узлов плюс 6 вершин ( где и находятся
наши  символы  ) , всего 11 . 4  байта  11  раз - 44 . Если мы добавим после
небольшое  количество  байтов  для  сохранения места узла и некоторую другую
статистику - наша  таблица  будет приблизительно 50 байтов длинны.
    Добавив  к  30 байтам сжатой информации, 50 байтов таблицы получаем, что
общая  длинна   архивного   файла   вырастет  до  80  байт .  Учитывая , что
первоначальная  длинна  файла  в  рассматриваемом примере была 100 байт - мы
получили 20% сжатие информации.
    е плохо . То  что  мы  действительно выполнили - трансляция символьного
ASCII  набора  в  наш  новый  набор  требующий  меньшее количество знаков по
сравнению с стандартным.
    Что мы можем получить на этом пути ?
    Рассмотрим  максимум  которй  мы  можем получить для различных разрядных
комбинацй в оптимальном дереве, которое является несимметричным.
    Мы получим что можно иметь только :

                 4 - 2 разрядных кода;
                 8 - 3 разрядных кодов;
                16 - 4 разрядных кодов;
                32 - 5 разрядных кодов;
                64 - 6 разрядных кодов;
               128 - 7 разрядных кодов;

     еобходимо еще два 8 разрядных кода.

                 4 - 2 разрядных кода;
                 8 - 3 разрядных кодов;
                16 - 4 разрядных кодов;
                32 - 5 разрядных кодов;
                64 - 6 разрядных кодов;
               128 - 7 разрядных кодов;
             --------
               254

     Итак  мы  имеем  итог  из   256  различных  комбинаций  которыми  можно
кодировать  байт .  Из  этих  комбинаций  лишь  2  по  длинне равны 8 битам.
Если  мы  сложим  число  битов которые  это представляет, то в итоге получим
1554 бит или 195 байтов. Так  в максимуме , мы сжали 256 байт к 195 или 33%,
таким  образом  максимально  идеализированный Huffman может достигать сжатия
в 33% когда используется на уровне байта .
     Все  эти  подсчеты  производились для не префиксных кодов Хаффмана т.е.
кодов, которые  нельзя идентифицировать однозначно. апример код A - 01011 и
код B - 0101 . Если мы будем получать эти коды побитно, то получив биты 0101
мы  не  сможем сказать какой код мы получили A или B , так как следующий бит
может  быть  как  началом  следующего  кода, так и продолжением предыдущего.
     еобходимо  добавить,  что  ключем к построению префиксных кодов служит
обычное  бинарное  дерево  и  если внимательно рассмотреть предыдущий пример
с  построением  дерева ,  можно  убедится ,  что  все  получаемые  коды  там
префиксные.
     Одно  последнее  примечание - алгоритм  Хаффмана требует читать входной
файл  дважды , один  раз  считая  частоты  вхождения  символов , другой  раз
производя непосредственно кодирование.

          
<== Back to main page