Понимание логики извлечения частоты из двоичного файла для создания дерева Хаффмана

Мне нужно вычислить частоту из бинарных файлов.

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

struct Node 
{
   unsigned char symbol;
   int appear;
   struct Node *link;
   struct Node * left,*right;
 };Node * head;

Где-то в основном мне нужно прочитать файл:

   ch = fgetc(fp); 
   while (fread(&ch,sizeof(ch),1,fp))
    {
     symbol(ch);    
    }
    fclose(fp);

где функция add_symbol выглядит так:

Но я не могу понять логику этого кода. Может ли кто-нибудь объяснить вопросы, которые я задал в коде?

symbol(unsigned char sym) 
{
    Node*pt,*pt,*t;
    int is_there=0;
    pt = pt = head;
    while (pt != NULL) 
    { 
        if (pt -> symbol == sym) 
        {
            pt -> appear++;
            is_there = 1;
            break;
        }
        pt = pt;
        pt = pt -> link;
    }
    if (!is_there)
    {
        //  printf("\n is_there2 : %d\n",!is_there);
        printf("sym2 : %d\n", sym);
        t = (Node *) malloc(sizeof( Node));
        t -> symbol = sym;
        t -> appear = 1;
        t -> left = NULL;
        t -> right = NULL;
        t->link = NULL;
        if (head == NULL) 
        {
            head = temp;
        }
        else
        {
            pt->link = temp;
        }
    }
}

Чтобы найти ту же частоту, нам нужно сначала где-то сохранить все данные.

(1) Где это делается?

(2) Нам нужно сравнить символ, появляется ли он снова или нет?

(3) Пожалуйста, объясните немного больше кода, логика одинакова и в c, и в c++. Так что любой язык, никаких проблем.

В объяснении у меня есть сомнения, что: предположим, что 1 2 1 3 3 1 2 - это символы в двоичном файле. При первом выполнении addsymbol мы делаем addsymbol(1); , Теперь мы сохраняем «1», чтобы знать, появится ли какая-либо другая «1» в будущем или нет? так что делаем pt->symbol если снова равно "1" то увеличиваем частоту на единицу. Но при втором выполнении addsymbol мы делаем addsymbol(2); который не равен "1", поэтому снова повторите.

При третьем выполнении я получил addsymbol(1); , на этот раз я получил «1», что равно «1», сохраненному ранее, поэтому частота увеличивается на «1». А как насчет предыдущей "2"? Поскольку мы читаем файл только один раз, выполнив

while (fread(&ch,sizeof(ch),1,fp))
    {
     add_symbol(ch);    
    }

и если "2" уже пройдено, то мы не сможем его посчитать. Как этот код сохраняет это «2», а также находит его частоту. Пожалуйста, не стесняйтесь спрашивать меня, если вы все еще не понимаете мой вопрос?


person user252990    schedule 27.02.2014    source источник
comment
Это действительно ваш код или вы просто скопировали его откуда-то? Вы пытаетесь понять, как работает реализация дерева кодирования Хаффмана, или просто понять реальную реализацию этого?   -  person StephenH    schedule 27.02.2014
comment
Есть очень мало заданий и сравнений, которые являются кандидатами на вопросы 1 и 2. Это не должно быть так сложно решить. (Кстати: кажется, вы подразумеваете, что написали этот код, но все же спрашиваете, как он работает?)   -  person molbdnilo    schedule 27.02.2014
comment
@StephenH Я пытаюсь понять этот код, он не мой   -  person user252990    schedule 27.02.2014
comment
@user252990 user252990, если вам нужно написать частотный калькулятор, как реально поможет копирование чужого кода? Вам нужно понять принципы дерева Хаффмана, прежде чем вы поймете программную реализацию.   -  person StephenH    schedule 27.02.2014
comment
@StephenH Я отредактировал вопрос, пожалуйста, попытайтесь понять мой вопрос. Дело не в Хаффмане. Речь идет о вычислении частоты (количества повторений символов). Я уже создал дерево Хаффмана, используя файл Input.txt в качестве единственного аргумента, содержащего алфавиты, такие как aababccaddea, там я сначала вычислял количество алфавитов. предположим, что число = размер, затем с этим размером я вычислял частоту каждого символа, сохраняя их и делая сравнения с плохой сложностью. Но это новый способ, которым я пытаюсь понять. Не могли бы вы помочь мне здесь по моему вопросу? Спасибо.   -  person user252990    schedule 27.02.2014
comment
Нет необходимости хранить частоты данных. Достаточно просто сохранить дерево Хаффмана. Посмотрите в документах zlib, как можно идеально восстановить дерево Хаффмана просто по длине каждого символа.   -  person Aki Suihkonen    schedule 27.02.2014


Ответы (2)


Код не хранит все данные, он хранит только символы и счетчики в связанном списке.

Код считывает по одному символу, вызывая add_symbol() для каждого. Функция add_symbol начинается с поиска символа в связанном списке. Если символ есть, функция просто увеличит его счетчик; в противном случае он добавит символ в хвост списка и со счетом 1.

Редактировать. По запросу, вот как это выглядело бы, если бы оно было более декомпозировано:

void Huffman::add_symbol(unsigned char sym)
{
    Node * foundNode = find_node_in_linked_list(sym);
    if(foundNode != NULL)
        foundNode->freq++;
    else
        add_freq1_node_at_end_of_list(sym);
}

Node* Huffman::find_node_in_linked_list(unsigned char sym)
{
    Node* pCur = Start;
    while(pCur != NULL)
    {
        if(pCur->symbol == ch)
            return pCur;
        pCur = pCur->next;
    }
    return NULL;
}

void Huffman::add_freq1_node_at_end_of_list(unsigned char sym)
{
    //Get tail of list
    Node* pTail = NULL;
    Node* pCur = Start;
    while(pCur != NULL)
    {
        pTail = pCur;
        pCur = pCur->next;
    }
    //Now, pTail is either the last element, or NULL if the list is empty.

    //Create the new object
    //(should use the new keyword instead, but since the deletion code was not posted...
    Node* pNew = static_cast< Node* >(malloc(sizeof *pNew)); 
    if(pNew == NULL)
        return;

    pNew->symbol = sym;
    pNew->freq = 1;
    pNew->left = NULL;
    pNew->right = NULL;
    pNew->next = NULL;
    pNew->is_processed = 0;

    //Add the new node at the tail
    if(pTail != NULL)
        pTail->next = pNew;
    else
        Start = pNew;
}

Обратите внимание, что она менее эффективна, чем функция big, потому что она проходит по списку дважды, когда символ не найден (один раз, чтобы попытаться найти символ, и один раз, чтобы найти хвост).

На самом деле, нет причин специально добавлять в хвост, а не вставлять в голову.


Откровенно говоря, связанный список — не самый эффективный способ хранения счетчиков до 256 символов. Лично я бы рекомендовал вместо этого использовать таблицу поиска (немой вектор из 256 структур или даже специальный объект гистограммы, который был бы просто вектором из 256 целых чисел).

person Medinoc    schedule 27.02.2014
comment
Спасибо за помощь. Допустим, в файле хранится 12133142342 символа. В первый раз передается 1 addsymbol(1); и он сохраняется в ptr-›symbol. в следующий раз addsymbol(2); но 2 не равно 1, поэтому не принимайте это во внимание, а в следующий раз добавьте символ (1); , Теперь он нашел 1, поэтому увеличьте частоту на единицу. (Вопрос1:) А как насчет двух предыдущих, которые мы получили при втором выполнении addasymbol(); который не был сохранен, потому что его частота не была равна 1? (вопрос: 2) Нужно ли нам снова проверять все 2 (и каждый символ) все символы, чтобы сохранить его частоту - person user252990; 27.02.2014
comment
Ваше предложение «но 2 не равно 1, так что не принимайте это во внимание» полностью ложно. Истина «, но 2 не равно 1, поэтому продолжайте перебирать связанный список, пока не найдете 2; а если не найдено, добавить с частотой 1. » - person Medinoc; 27.02.2014
comment
Каждый вызов addsymbol всегда выполняется следующим образом: 1, пройтись по связанному списку из головы (здесь он указан start), чтобы найти, существовал ли символ sym в этом списке; 2, если да, добавьте частоту (pt -> freq++;) и установите флаг is_there для возврата из этой функции; 3, если нет, в цикле while ppt будет указывать на последний узел этого списка, а is_there будет false. Затем malloc создаст новый узел и запишет частоту sym с помощью 1 (здесь temp -> freq = 1;) - person Charlie; 27.02.2014
comment
Medinoc (1) Так в чем сложность этой процедуры? (2) Я отредактировал свой вопрос, чтобы получить ответ точно по делу. Спасибо за попытку помочь мне. - person user252990; 27.02.2014
comment
O(N) для каждого поиска, N — это количество различных символов. Поскольку M является длиной файла, мы получаем O(N*M) для всей конструкции гистограммы, а использование таблицы поиска сделает ее O(M) - person Medinoc; 27.02.2014
comment
@Medinoc, не могли бы вы написать для него тот же код, но простой программный код или алгоритм? Очень сложно понять логику. Я до сих пор не могу ее понять. Спасибо. - person user252990; 27.02.2014

Несколько советов по общему дизайну:

Шаг №1: Для подсчета символов вы можете использовать простую гистограмму:

include <limits.h>
int histogram[1<<CHAR_BIT] = {0};
unsigned char ch;
while (fread(&ch,sizeof(ch),1,fp))
    histogram[ch]++;

Шаг № 2: Теперь вам нужно использовать гистограмму, чтобы построить дерево Хаффмана:

  • Создайте массив указателей Node, по одному для каждой записи в histogram со значением больше 0.
  • Возьмите этот массив и постройте двоичную кучу с минимальным значением вверху.
  • Run the following algorithm, until there is one element left in the heap:
    • Extract the first two Node elements from the heap.
    • Создайте новый Node, дочерними элементами которого являются эти два элемента Node.
    • Вставьте новый Node обратно в кучу.

Шаг № 3: Теперь, когда у вас есть дерево Хаффмана, обратите внимание на следующее:

  • Чтобы закодировать файл, вам нужно использовать листья дерева (данные в массиве Node указателей, созданных в начале предыдущего шага).
  • Чтобы декодировать файл, вам нужно использовать корень дерева (который является последним элементом, оставшимся в куче в конце предыдущего шага).

Вы можете увидеть полный пример по адресу:

http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=9737&lngWId=3.

person barak manos    schedule 27.02.2014
comment
Спасибо за помощь, но я обязан не использовать статическое объявление (без массивов), а только указатели. Я уже сделал то, что вы сказали, используя массив. Это мой следующий шаг, чтобы правильно понять концепции указателей. - person user252990; 27.02.2014
comment
Это неплохой ответ, но мне грустно, что вы продолжаете давнюю традицию продвижения побитового декодирования (я скачал код, чтобы посмотреть, что он делает). Побитовое декодирование должно быть не чем иным, как первым шагом в изучении правильных алгоритмов декодирования. - person harold; 27.02.2014
comment
Нет, все практические реализации используют декодирование на основе таблиц (и кодирование на основе таблиц, конечно, тривиально). - person harold; 27.02.2014
comment
Но мы говорим здесь о кодировании/декодировании Хаффмана, не так ли? - person barak manos; 27.02.2014
comment
Да, конечно, но вместо того, чтобы ходить по дереву, вы просто выполняете поиск в таблице. Самый простой вариант — иметь одну большую таблицу длиной 2 ^ max_symbol_length, заполненную записями, в которых указано, какой первый символ находится в индексе и сколько бит вам нужно сдвинуть. Затем вы просто индексируете эту вещь своим буфером. Есть несколько трюков, чтобы использовать меньше места. - person harold; 27.02.2014
comment
Окей, попался. На самом деле такой подход звучит довольно тривиально. Думаю, я этого не сделал, потому что в основном я сосредоточился на алгоритме Хаффмана... Спасибо за информацию :) - person barak manos; 27.02.2014
comment
Да и кодирование еще проще, достаточно собрать все паттерны и их длины в два массива за один проход по дереву (если вы используете канонические коды Хаффмана, что вам и следует делать, вам нужны только длины и потом генерировать канонические выкройки из них) - person harold; 27.02.2014
comment
Ой, подождите, а как насчет декодирования закодированного файла? Как избежать обхода битов в этом файле (т. е. как узнать, когда взять закодированную последовательность битов и найти соответствующий декодированный символ)? - person barak manos; 27.02.2014
comment
@barakmanos Не могли бы вы переписать код addsymbol() в моем коде, используя немного простой алгоритм c/c++/или? Мне очень сложно это понять. Спасибо за это. - person user252990; 27.02.2014
comment
Хорошо, комментариев становится слишком много, я рекомендую прочитать это и это и это, и если вам нужна довольно быстрая реализация, у меня есть для вас здесь (включает некоторые вещи, которые не являются кодами Хаффмана, но вы можете их выделить) - person harold; 27.02.2014
comment
Спасибо, @harold, в настоящее время у меня нет таких потребностей, но я ценю просветление. Еще раз спасибо за информацию :) - person barak manos; 27.02.2014
comment
@harold Не могли бы вы переписать код addsymbol() в моем коде, используя немного простой алгоритм c/c++/или? Мне очень сложно это понять. Спасибо за это. - person user252990; 27.02.2014