Алгоритм генерации двоичных кодов префиксов - PullRequest
5 голосов
/ 06 сентября 2011

A Код префикса - это набор кодов, так что ни один код не является префиксом другого кода.Например, следующий набор является кодом префикса:

10
11
000
001
0100
0101
0110
0111

С n = 8 членами.Я думаю, что они обычно создаются с использованием некоторого типа дерева Хаффмана.

Мой вопрос: не могли бы вы помочь мне создать функцию, которая будет генерировать двоичный код префикса с 'n' членами?

Что-токак это:

list<int> GenerateBinaryPrefixCodes(int n);

Кроме того, требуется, чтобы он был «оптимальным» в том смысле, что общая сумма битов минимизирована.

Я бы предпочелответ на C / C ++ / C # / нечто подобное.Это на самом деле не домашняя работа, но я пометил ее так, потому что звучит так, будто это будет хорошей проблемой.

Спасибо!

Ответы [ 6 ]

4 голосов
/ 07 сентября 2011

Коды префиксов

Как вы указали, код префикса - это код, в котором данный код не является префиксом для любого другого заданного кода. Это очень общее определение. Кодировка Хаффмана - это ограниченная форма префиксного кода.

Обычное использование для кодирования Хаффмана - минимизация (оптимизация) общего количества битов, необходимых для кодирования «сообщения». «Сообщение» обычно представляет собой последовательность символов, и оно кодируется путем сопоставления каждого вхождения символа с конкретный префиксный код и запись префиксного кода на его место. Можно использовать любой набор префиксных кодов. сделать это. Но кодирование Хаффмана приведет к получению кратчайшего сообщения на основе количества битов.

Например, набор символов ASCII можно рассматривать как отображение символов на набор 8-битных префиксных кодов. Это можно даже считать кодировкой Хаффмана при условии, что закодированное сообщение содержит точно одинаковое количество каждого возможного символа.

Интересный материал начинается, когда кодируемое сообщение содержит неравные частоты символов. В этот Во-первых, можно уменьшить общую длину сообщения в битах, используя префиксные коды различной длины. Используйте короткие префиксные коды для более частых символов и более длинные префиксные коды для менее частых символов.

Из вашего примера есть 8 символов для кодирования. Символы, сопоставленные с префиксными кодами «11» и «10», будут наиболее частые символы в сообщении. Аналогично, символы, отображаемые на «0111», «0110», «1010» и «0100», будут наименее частыми. Чем выше частота, тем короче код префикса.

«Уловка» в создании кодирования Хаффмана состоит в том, чтобы создать набор кодов префиксов таким образом, чтобы после отображения каждый символ в сообщении связан с префиксными кодами, которые сообщение содержит как можно меньше битов.

Я считаю полезным просматривать префиксные коды в виде двоичного дерева, где каждый листовой узел отображается на символ. Например, двоичное дерево, соответствующее префиксным кодам, приведенным в вашем вопросе (01, 11, 000, 001, 0100, 0101, 0110, 0111) будет:

           +-- (11)
        +--+
        |  +-- (10)
        |
        |        +-- (0111)
      --+     +--+
        |     |  +-- (0110)
        |  +--+
        |  |  |  +-- (0101)
        |  |  +--+
        +--+     +-- (0100)
           |
           |  +-- (001)
           +--+
              +-- (000)

Чтобы получить значения в скобках, вы просто присваиваете «1», когда следует верхний край, или «0», если нижний край сопровождается.

Как построить такое дерево?

Начнем со структур данных, представляющих двоичное дерево и список.

Двоичное дерево будет содержать два типа узлов. 1) Конечный узел, представляющий символ и его частота и 2) внутренний узел, представляющий совокупную частоту из всех узлов под ним (ему также нужны два указателя, один для левой ветви и один для правой ветви).

Список содержит упорядоченный набор узлов из двоичного дерева. Узлы в списке упорядочены на основе значения частоты узла, на который они указывают. Узлы с самой низкой частотой встречаются в начале списка и увеличьте к концу списка. Связанный список указателей на узлы дерева может быть полезным реализация - но подойдет любая упорядоченная структура списка.

В приведенном ниже алгоритме используются два списка: «справочный» и «рабочий» список. Как узлы обработанные из «справочного» списка, новые узлы создаются и вставляются в «рабочий» список таким образом, чтобы «рабочий» список остается упорядоченным по частоте узла.

Используйте эти структуры данных и следующий алгоритм для создания кодировки Хаффмана.

0. Initialize the "reference" list by creating a leaf node for each symbol
   then add it into this list such that nodes with the lowest frequency 
   occur at the front of the list and those with the highest frequency
   occur at the back (basically a priority queue).

1. Initialize the "working" list to empty.

2. Repeat until "reference" list contains 1 node

   2.1 Set MaxFrequency to the sum of the first 2 node frequencies

   2.1 Repeat until "reference" list is empty
       If ("reference" list contains 1 node) OR
          (sum of the next two nodes frequency > MaxFrequency)
            Move remaining nodes to the "working" list
            Set "reference" list to empty
       Else
          Create a new internal node
          Connect the first "reference" node to the left child
          Connect the second "reference" node to the right child
          Set the new node frequency to the sum of the frequencies of the children
          Insert the new node into the "working" list
          Remove the first and second nodes from the "reference" list

   2.2 Copy the "working" list to the "reference" list
   2.3 Set the "working" list to empty

В конце этого процесса отдельный элемент «ссылки» будет корнем дерева Хаффмана. Вы можете перечислить Префикс кодирует, выполняя первый обход дерева по глубине. Запишите '0' для каждой левой ветви принято и «1» для каждой правой ветви. Код завершен, когда лист встречается. Символ на лист кодируется только что сгенерированным кодом Хаффмана.

Какая оптимальная кодировка

Интересное вычисление, которое можно выполнить, - это вычисление «битового веса» кодирования префикса. Немного вес это общее количество бит, необходимое для представления набора префиксных кодов.

Посмотрите на свое оригинальное дерево выше.Вес этого дерева составляет (2 бита * 2) + (4 бита * 5) + (3 бита * 2) = 30 бит.Вы использовали 30 бит для представления 8 значений префикса.Какое минимальное количество битов вы могли бы использовать?Подумайте об этом, поскольку дерево становится неуравновешенным, длина пути к некоторым листьям становится длиннее - это увеличивает вес.Например, наихудший случай для дерева 4-значного префикса:

                 +-- (1 bit)
               --+                  
                 |  +-- (2 bits)
                 +--+
                    |  +-- (3 bits)
                    +--+
                       +-- (3 bits)

, что дает общий вес (1 бит * 1) + (2 бита * 1) + (3 бита * 2) = 9биты

Балансировать дерево:

                +-- (2 bits)
             +--+
             |  +-- (2 bits)
           --+  
             |  +-- (2 bits)
             +--+
                +-- (2 bits)

, что дает общий вес (2 бита * 4) = 8 бит.Обратите внимание, что для сбалансированных деревьев все префиксные коды имеют одинаковое количество битов.

Вес бита дерева - это просто сумма длин пути ко всем листьям.Вы минимизируете вес в битах, минимизируя общую длину пути - и это делается путем балансировки дерева.

Как видите, в минимизации любого заданного префиксного дерева нет особой ценности, вы просто получаетекодирование символов фиксированной длины.Значение приходит, когда вы учитываете битовый вес результирующего закодированного сообщения.Сведение к минимуму приводит к кодированию Хаффмана.

Сколько существует различных кодировок?

Префиксные коды могут быть сгенерированы путем обхода двоичного дерева и выдачи '0' для каждогозатем следовала нижняя ветвь и «1» для каждой верхней ветки, пока не встретился лист.Как в:

             +--+ (1)
             |  
           --+  
             |  +-- (01)
             +--+
                +-- (00)

В качестве альтернативы мы можем «перевернуть» это правило и назначить «1» для каждой нижней ветви и «0» для верхней ветви:

             +-- (0)
             |  
           --+  
             |  +-- (10)
             +--+
                +-- (11)

Этигенерировать два разных набора префиксных кодов.Дополнительные наборы могут быть сгенерированы путем прохождения всех возможных 1/0 назначений ветвям и последующего обхода дерева.Это даст вам 2 ^ n наборов.Но если вы сделаете это, вы обнаружите, что одинаковые префиксные коды могут быть сгенерированы, но в другом порядке.Например, предыдущее дерево даст следующие наборы: {(0, 10, 11), (0, 11, 01), (1, 01, 00), (1, 00, 01)}.Затем переверните дерево:

                +-- (??)
             +--+
             |  +-- (??)
           --+
             |
             +-- (?)

, и вы получите: {(11, 10, 0), (10, 11, 0), (01, 00, 1), (00, 01, 1))}.Положите их вместе на 2 ^ 3 = 8 комплектов.Однако если вы хотите, чтобы уникальные наборы не учитывали порядок, есть только 2 набора: {(0, 10, 11), (1, 00, 01)}.Пройдите то же упражнение для сбалансированного дерева, и есть только 1 сет.Все это приводит меня к мысли, что количество уникальных кодировок связано со структурой баланса дерева, используемого для генерации префиксных кодов.К сожалению, у меня нет точной формулы или расчета.На догадке я бы предположил, что число будет 2 ^ (количество различных длин кода - 1).Для сбалансированного дерева это: 2 ^ (1 - 1) = 1;для дерева с двумя разными длинами кода (как в примере выше): 2 ^ (2 - 1) = 2;и для вашего примера: 2 ^ (3 - 1) = 4.

4 голосов
/ 06 сентября 2011

Требование минимизации суммы количества битов эквивалентно требованию, чтобы коды были оптимальными кодами Хаффмана для строки, где каждый символ встречается один раз. Поэтому просто создайте строку с n уникальными символами и создайте для нее дерево Хаффмана. Алгоритм описан в Википедии .

1 голос
/ 06 сентября 2011

Ваш пример для n = 8 не является оптимальным решением.

10 11 000 001 0100 0101 0110 0111 Всего битов: 26

000 001 010 011100 101 110 111 Всего битов: 24

При постоянной частоте оптимальное кодирование префикса будет фиксированной длины.Каждый код префикса будет иметь длину log (n) и будет двоичным представлением алфавита от 0..n-1.

EDIT для случая, когда n НЕ является степенью2.

// generate tree
function PCode(n) {
 var a = [];
 for(var x=1; x<=n; x++) {
  a.push({"v":x});
 }
 for(var x=0; x<n-1; x++) {
  var node = {"v": null, "l": a.shift(), "r": a.shift()};
  a.push(node);  
 }
 return a.pop();
}

//print
function Print(node, s) {
 if(node["v"] != null) {
  console.log(s);
 }
 if(node["l"] != null) Print(node["l"], s + "0");
 if(node["r"] != null) Print(node["r"], s + "1");
 return;
}

//test
Print(PCode(3), "");
0 голосов
/ 07 сентября 2011

Давайте закодируем двоичную строку x числом, двоичное представление которого равно 1x. В противном случае 0 и 00 будут отображаться в одно и то же целое число.

std::vector<int> GenerateBinaryPrefixCodes(int n) {
    std::vector<int> list;
    for (int i = n; i != 2 * n; ++i) list.push_back(i);
    return list;
}
0 голосов
/ 06 сентября 2011

Проблема генерации (уникальность декодирования) может быть гарантирована путем построения двоичного дерева из n конечных узлов и перечисления положения каждого такого узла в дереве (0 - левая ветвь, 1 - правая ветвь).И вы правы, у Хаффмана есть это свойство.Обратите внимание, что для деревьев Хаффмана каждому узлу присваивается вес, равный частоте его репрезентативного символа, а дерево строится с рекурсивным свойством, согласно которому лево-правое решение о соединениях узлов основывается на сумме дочерних элементов в этой точке,Это свойство накопленной суммы также объясняет, почему распределение Фибоначчи дает сжатие в худшем случае для деревьев Хаффмана.

Обратите внимание, что кодирование Хаффмана является оптимальным для переменного кодирования фиксированных алфавитов.Примером нефиксированного алфавита является решение рассматривать «the» как один элемент в вашем наборе, который нужно сжать (в отличие от двух пробелов и по одному на каждую из букв).

Ваша проблема не связана с заменой.Вам просто нужны префиксные коды для n элементов, в которых сумма длин всех префиксных кодов минимизирована.Это то же самое, что построение дерева Хаффмана, где частота каждого элемента равна 1 (потому что это гарантирует минимальное кодирование всей кодированной строки, которое для вас равно сумме битов каждого кодированного элемента ровно один раз, т.е. минимизирует общее количествобиты).Примечание: это гарантирует минимальное кодирование, но не гарантирует самую быструю реализацию.Вероятно, вам не нужно строить дерево для каждого вызова метода.К сожалению, я не знаю реализацию на макушке.

0 голосов
/ 06 сентября 2011

Пожалуйста, посмотрите на этот учебный сайт по C ++ .Он предоставит вам полезные структуры C ++.И я вижу другие подобные вопросы SO, которые могут быть полезны в разделе «Связанные» справа.

Я делал это раньше в C с помощью рекурсивного алгоритма, и да, это было бы здороводомашнее задание.

...