Редактирование: Оптимальное хранение словаря в алгоритме Хаффмана

Перейти к: навигация, поиск

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

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 1: Строка 1:
 
При сжатии данных [[Алгоритм Хаффмана|алгоритмом Хаффмана]] появляется проблема передачи оптимального кода зашифрованного сообщения, который поможет получателю расшифровать его. Рассмотрим некоторые варианты решения этой задачи.
 
При сжатии данных [[Алгоритм Хаффмана|алгоритмом Хаффмана]] появляется проблема передачи оптимального кода зашифрованного сообщения, который поможет получателю расшифровать его. Рассмотрим некоторые варианты решения этой задачи.
  
{{Задача
+
== Постановка задачи ==
|definition =
+
 
 
Пусть у нас есть алфавит <tex>\Sigma = \{a_1, a_2, \cdots, a_n\}</tex>, <tex>|\Sigma| = n</tex>, и код <tex>c</tex>, сопоставляющий каждому символу <tex>a_i</tex> его код <tex>c_i</tex>.
 
Пусть у нас есть алфавит <tex>\Sigma = \{a_1, a_2, \cdots, a_n\}</tex>, <tex>|\Sigma| = n</tex>, и код <tex>c</tex>, сопоставляющий каждому символу <tex>a_i</tex> его код <tex>c_i</tex>.
 +
 
Нужно придумать эффективное представление кода <tex> c </tex> в памяти.
 
Нужно придумать эффективное представление кода <tex> c </tex> в памяти.
}}
 
  
 
== Простое решение ==
 
== Простое решение ==
Строка 58: Строка 58:
  
 
==== Вторая модификация ====
 
==== Вторая модификация ====
 +
 
Модифицируем значение команды <tex>U</tex>. Пусть теперь символ <tex>U</tex> значит, что мы поднимаемся к предку текущей вершины, пока мы — правый ребенок или пока не достигли корня, и если мы, остановившись, пришли из левого сына вершины, перейдем по ребру в правого ребенка. После такой модификации записывая каждый символ мы ровно по одному ребру проходим в первый раз.
 
Модифицируем значение команды <tex>U</tex>. Пусть теперь символ <tex>U</tex> значит, что мы поднимаемся к предку текущей вершины, пока мы — правый ребенок или пока не достигли корня, и если мы, остановившись, пришли из левого сына вершины, перейдем по ребру в правого ребенка. После такой модификации записывая каждый символ мы ровно по одному ребру проходим в первый раз.
 +
 
Конечный вариант кодировки дерева:
 
Конечный вариант кодировки дерева:
 +
 
<tex>DUDDUUU</tex>
 
<tex>DUDDUUU</tex>
 +
 
Распишем подробнее:
 
Распишем подробнее:
{| class=wikitable
+
 
 +
{| class="wikitable"
 
| <tex>D</tex> || Спускаемся влево
 
| <tex>D</tex> || Спускаемся влево
 
|-
 
|-
Строка 77: Строка 82:
 
| <tex>U</tex> || Поднимаемся до корня
 
| <tex>U</tex> || Поднимаемся до корня
 
|}
 
|}
 +
 
Оценим используемое количество памяти. Так как в нашем дереве <tex> n </tex> листьев, то в нем <tex> 2 \cdot n - 2 </tex> ребер (это легко показать из алгоритма Хаффмана, в нем <tex> n - 1 </tex> итерация и на каждой в дерево добавляется по два ребра), а символов в нашей записи будет <tex> 2 \cdot n - 1 </tex>, так как на каждое ребро приходится по символу плюс последний, терминальный, <tex> U </tex>.
 
Оценим используемое количество памяти. Так как в нашем дереве <tex> n </tex> листьев, то в нем <tex> 2 \cdot n - 2 </tex> ребер (это легко показать из алгоритма Хаффмана, в нем <tex> n - 1 </tex> итерация и на каждой в дерево добавляется по два ребра), а символов в нашей записи будет <tex> 2 \cdot n - 1 </tex>, так как на каждое ребро приходится по символу плюс последний, терминальный, <tex> U </tex>.
Заметим, что терминальный <tex>U</tex> нам вообще говоря не нужен: мы всегда в конце поднимаемся в корень дерева, а значит, если все символы прочитаны — то обход закончен. Итоговая оценка памяти: <tex> 2 \cdot n - 2 </tex>.
 
  
 
=== Передача информации для восстановления листьев ===
 
=== Передача информации для восстановления листьев ===
Алфавит нам будет дан изначально, не будет лишь кодов каждого символа, следовательно нам нужно просто указать, какой лист в дереве соответствует каждому символу. Занумеруем подряд все символы алфавита. Сопоставим каждому символу алфавита код фиксированной <tex>c'</tex> длины <tex> \lceil \log _2 \rceil n</tex> — его порядковый номер в алфавите, записанный в двоичной системе счисления. Все коды имеют фиксированную длину, а значит легко понять где заканчивается один, и начинается другой код. Тогда, если выписать подряд коды <tex>c'</tex> для всех символов в том порядке, в котором обход в глубину посещает соответствующие им листы, несложно восстановить, какому символу какой код <tex>c</tex> соответствует. Когда мы, в результате обхода в глубину, пришли в вершину, у которой нет детей, мы возьмем следующий переданный нам номер, посмотрим в нашем алфавите что это за символ, и присвоим текущей вершине этот символ.
 
  
=== Отсутствие некоторых символов в тексте ===
+
Алфавит у нас будет изначально, не будет лишь кодов каждого символа, следовательно нам нужно просто указать, какой лист в дереве соответствует каждому символу. Занумеруем подряд все символы алфавита. Сопоставим каждому символу алфавита код фиксированной <tex>c'</tex> длины <tex> \lceil \log _2 \rceil n</tex> — его порядковый номер в алфавите, записанный в двоичной системе счисления. Все коды имеют фиксированную длину, а значит легко понять где заканчивается один, и начинается другой код. Тогда, если выписать подряд коды <tex>c'</tex> для всех символов в том порядке, в котором обход в глубину посещает соответствующие им листы, несложно восстановить, какому символу какой код <tex>c</tex> соответствует. Когда мы, в результате обхода в глубину, пришли в вершину, у которой нет детей, мы возьмем следующий переданный нам номер, посмотрим в нашем алфавите что это за символ, и присвоим текущей вершине этот символ.
Предположим теперь, что не все символы из алфавита были использованы в тексте, тогда возникает вопрос: получив очередной код, как узнать какому символу он принадлежит? Для решения этой проблемы поступим следующим образом: выпишем подряд коды <tex>c'</tex> в том порядке, в котором обход в глубину посещает соответствующие им листы, лишь для тех символов, что были использованы в нашем сообщении. Тогда встретив очередной символ, мы гарантированно будем знать, что он встречался в нашем сообщении.
 
  
 
=== Используемая память ===
 
=== Используемая память ===
Строка 92: Строка 95:
 
Итого, задача решена с использованием <tex>n \lceil \log_2 \rceil n + 2n - 1 + 32 = n \lceil \log_2 \rceil n + 2n + 31 </tex> бит.
 
Итого, задача решена с использованием <tex>n \lceil \log_2 \rceil n + 2n - 1 + 32 = n \lceil \log_2 \rceil n + 2n + 31 </tex> бит.
  
Если же были использованы не все символы, то будет использовано <tex>k \lceil \log_2 \rceil n + 2k - 1 + 32 = k \lceil \log_2 \rceil n + 2k + 31 </tex> бит, где <tex>k</tex> — количество использованных символов.
+
=== Реализация ===
  
=== Реализация ===
+
   <span style="color:Green">// s - наша строка обхода, tree[] — дерево вершин, code — текущий код, alphabet[] — массив кодов символов, </span>
   <span style="color:Green">// s наша строка обхода, tree[] — дерево вершин, code — текущий код, alphabet[] — массив кодов символов, </span>
 
 
   <span style="color:Green">// c'[] — номера символов в порядке обхода</span>
 
   <span style="color:Green">// c'[] — номера символов в порядке обхода</span>
'''function''' huffman():
+
  '''function''' huffman():
  <span style="color:Green">//текущая вершина корень дерева</span>
+
    <span style="color:Green">//текущая вершина - корень дерева</span>
  curV = root
+
    curV = root  
  '''for''' i = 0..n - 2
+
    '''for''' i = 0..n - 2
    '''if''' s[i] == 'D'
+
      '''if''' s[i] == 'D'
      curV = tree[curV].leftChild
+
        curV = tree[curV].leftChild
      code += '0'
+
        code += '0'
      '''if''' curV не имеет детей
+
        '''if''' curV не имеет детей  
        alphabet[следующий номер c'].push(code)
+
          alphabet[следующий номер c'].push(code)
    '''else'''
+
      '''else'''
      '''while''' curV является правым ребенком и curV не корень
+
        '''while''' curV является правым ребенком и curV не корень
        curV = tree[curV].parent
+
          curV = tree[curV].parent
        удалить из code последний символ
+
          удалить из code последний символ
        curV = tree[curV].rightChild
+
        curV = tree[curV].rightChild  
        code += '1'
+
        code += '1'
        '''if''' curV не имеет детей
+
        '''if''' curV не имеет детей  
          alphabet[следующий номер c'].push(code)
+
          alphabet[следующий номер c'].push(code)
 +
 
 +
=== Смотрите также ===
  
== См. также ==
 
 
*[[Алгоритм_Хаффмана_за_O(n) | Алгоритм Хаффмана за O(n)]]
 
*[[Алгоритм_Хаффмана_за_O(n) | Алгоритм Хаффмана за O(n)]]
  

Пожалуйста, учтите, что любой ваш вклад в проект «Викиконспекты» может быть отредактирован или удалён другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. Викиконспекты:Авторские права). НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!

Чтобы изменить эту страницу, пожалуйста, ответьте на приведённый ниже вопрос (подробнее):

Отменить | Справка по редактированию (в новом окне)

Шаблон, используемый на этой странице: