了解从二进制文件中提取频率以创建霍夫曼树的逻辑

时间:2014-02-27 13:48:26

标签: c++ c algorithm binaryfiles huffman-code

我必须从二进制文件中剔除频率。

我想到的是我会读取文件中的字符,然后按字符重复的次数计算频率。 我这样做是使用此代码。它工作正常:

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->符号如果再次等于“1”那么我们将频率增加1。 但是在第二次执行addsymbol时,我们会添加addsymbol(2);这不等于“1”,所以再次重复。

第三次执行时我得到了addsymbol(1); ,这次我得到的“1”等于之前存储的“1”,所以频率增加“1”。 那之前的“2​​”怎么样?因为我们只通过

读取文件一次
while (fread(&ch,sizeof(ch),1,fp))
    {
     add_symbol(ch);    
    }

如果“2”已经通过,那么我们将无法计算它。这段代码如何保持这个“2”并且还发现它的频率请不要犹豫,问我是否还没有解决我的问题?

2 个答案:

答案 0 :(得分:3)

代码不存储所有数据,它只将符号和计数存储在链表中。

代码一次读取一个符号,为每个符号调用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;
}

请注意,它比大函数效率低,因为当找不到符号时它会通过列表两次(一次尝试找到符号,一次找到尾部)。

事实上,没有理由专门添加尾部而不是插入头部。


坦率地说,链表不是存储最多256个符号的计数的最省时的方法。我个人建议使用查找表(256个结构的哑向量,甚至是一个专用的直方图对象,它只是256个整数的向量)。

答案 1 :(得分:2)

有关您的总体设计的一些建议:

步骤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。
  • 获取此数组并构建一个顶部最小值的二进制堆。
  • 运行以下算法,直到堆中还剩下一个元素:
    • 从堆中提取前两个Node元素。
    • 创建一个新的Node,其子级是这两个Node元素。
    • 将新Node插回堆中。

步骤3:现在你有一张霍夫曼树,请注意以下几点:

  • 为了对文件进行编码,您需要使用树的叶子(在上一步开始时创建的Node指针数组中给出)。
  • 为了解码文件,您需要使用树的根(这是上一步结束时堆中剩余的最后一个元素)。

您可以在以下位置查看完整示例:

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