对小甲鱼哈弗曼树代码的小修改

只是记录一下自己写作业的过程,我不是一个程序员,我是一个想进德云社的闲散人员
一.自己对huffman树的理解
先用程序读入一篇或多篇英文文章,把文章中出现的每一个字符都当做树结构中的一个节点,并且给每个节点附上一个权值(该字符在文章中出现的次数),通过这些权值,构成最小生成树。这就是huffman树的建立过程。最小生成树建立好以后,每个字符都会有一个由二进制数构成的编码,可以根据这些编码对一串二进制树解码,把二进制数还原成字符。具体内容会在程序中讲解。

二.程序讲解

# include <stdio.h>
# include <stdlib.h>
# include "huffman.h"

int main()
{
    char *rf = read_file("1.txt");//读入文档内容
    htTree * codeTree = buildTree(rf);//建立huffman树
    hlTable *codeTable = buildTable(codeTree);//建立编码表
    char *enco = read_file("encode.txt");
    char* deco = encode(codeTable,enco);//对读入字符串编码
    decode(codeTree,deco);对读入字符串解码
    return 0;
}

1.read_file函数讲解
上面已经提到过huffman就是通过读入一写字符,根据每个字符的出现次数生成最小生成树,那么如何读入字符内容呢。我选择从txt文档里读入。先看一下这个函数的代码吧

char* read_file(char *fname)
{
    FILE*fp = fopen(fname,"rb");
    char arr[100][256];
    char *str;
    int len=0;
    int i=0;
    while(fgets(arr[i],256,fp)!=NULL)
      {
           i++;
      }
    fclose(fp);
    str = (char*)malloc(sizeof(char)*256*(i));
    memset(str,0,sizeof(char)*256*(i));
    for(int j=1;j<=i;j++)
    {
        strcat(str,arr[j-1]);
    }
    return str;
}

这里需要思考的一个问题是如何读入多行文档。用fgets函数可以很好解决。根据我个人了解,fgets函数每次都能读入一行文档,如果文档有多行,再次使用fgets函数读取文档时,就会读取下一行。直到 fgets(arr[i],256,fp)!=NULL的时候,说明多行文档已经读入完毕。每次读取一行内容的时候都会存到二维数组arr中。fgets函数会自动在第一个参数数组的末尾加上结束符’\0’.
于此同时通过i++,来得到当行文档总共有多少行。一次来获得要存入全部文档总共需要的一个大概内存。(char*)malloc(sizeof(char)256(i)),假设每行有256个字符,在这里肯定会浪费一些空间,这里就留给你们去修改。然后通过memset初始化字符串空间。
然后吧文档的每行内容存到str指向的空间。这样就完成了对当行字符的读入并用一个指针指向这些字符内容,并返回。

2.buildTree函数内容讲解

htTree * buildTree(char *inputString)//建立Huffman树
{

        int *probablity = (int*)malloc(sizeof(int)*256);//整数数组
    for(int i=0;i<256;i++)
    {
        probablity[i]=0;//整数数组初始化
    }
    for(int j=0;inputString[j]!='\0';j++)
    {
        probablity[(unsigned int)inputString[j]]++;//当读入的字符串不结束时,一直循环,对应ascall编码的整数下标的数组元素+1
    }//出现次数用作权值

    pQueue * huffmanqueue = NULL; 
    huffmanqueue = iniPQueue(huffmanqueue);//生成并初始化huffamn树队列

    for(int k=0;k<256;k++)
    {
        if(probablity[k]!=0)//!=0说明k整数对应的ascall码存在于字符串中
        {
            htNode *aux = (htNode*)malloc(sizeof(htNode));
            aux->left = NULL;
            aux->right = NULL;
            aux->symbol = (char)k; //将整数转换为字符

            addPQueue(&huffmanqueue,aux,probablity[k]);//Huffman树节点入队,数组元素的值即为权值,插入时就排序了
        }
    }


    free(probablity);//释放数组


    while(huffmanqueue->size!=1)//当队列只有一个节点时,那就是根节点
    {
        //两个节点合并
        int priority = huffmanqueue->first->priority;
        priority += huffmanqueue->first->next->priority;
        htNode *left = getPQueue(&huffmanqueue);//前两个链表节点出来
        htNode *right = getPQueue(&huffmanqueue);
        htNode *newNode = (htNode*)malloc(sizeof(htNode));//将两个节点权值相加,形成新的Huffman树节点
        newNode->left = left;//左右孩子赋值,左孩子权值小于右孩子
        newNode->right = right;
        addPQueue(&huffmanqueue,newNode,priority);//新节点加入链表,并排序
    }
    htTree *tree = (htTree*)malloc(sizeof(htTree));//创建Huffman树根节点
    tree->root = getPQueue(&huffmanqueue); //复制为huffman树的根节点
    printf("读入文档\n%s\nhuffman tree已经建立\n",inputString);

    return tree;//返回根节点
}

在这里用到了自定义的一些类型

typedef struct _htNode
{
    char symbol;  //存节点数据
    struct  _htNode *left,*right;//指向左右节点的指针
}htNode;
typedef struct _htTree
{
    htNode *root;//存储Huffman树的根节点
}htTree;
typedef struct _hlNode  //生成最小生成树的队列中的节点结构
{
    char symbol;      //数据
    char *code;    //存入该字符的huffman树的编码
    struct _hlNode *next; //链表队列的next指针
}hlNode;
typedef struct _pQueueNode
{
    htNode* val; //存入一个树节点
    unsigned int priority;//存入节点权值
    struct _pQueueNode *next;
}pQueueNode;
typedef struct _pQueue
{
    unsigned int size;  //队列的大小
    pQueueNode *first;//队列的第一个节点
}pQueue;

ascll字符总共有256个,所以这里有先定义一个大小为256的int数组,并都复制为0。读入整个文档内容,把文档内字符的int型值当做下标,某一字符每出现一次,该字符对应下标的值就+1,来记录每个字符的出现次数。如果probablity[k]==0,说明int型k对应的char类型字符在读入的文档里并没有出现。就不做处理。对于文档里出现的字符成为huffman树节点。节点类型为htNode,htNode->symbol保存该字符,symbol->*left,symbol->*right,保存左右孩子。然后加入自定义类型队列。
先看一下队列的初始化

pQueue * iniPQueue(pQueue *queue)
{
    (queue) = (pQueue*)malloc(sizeof(pQueue));
    (queue)->first = NULL;
    (queue)->size = 0;
    return queue;
}

然后把每个huffman树节点存入队列并排序。这里存入并排序用到了 addPQueue函数

void addPQueue(pQueue **queue,htNode* val,unsigned int priority)
{
        if((*queue)->size == MAX)//队满,返回
    {

        printf("\n nQueue is full \n");
        return ;
    }


    pQueueNode *aux = (pQueueNode*)malloc(sizeof(pQueueNode));


    aux->val = val;  //队列节点val成员赋值为传入的huffamn树的节点指针
    aux->priority = priority;//权值

    if((*queue)->size==0 || (*queue)->first == NULL)//此时为插入第一个节点
    {
        aux->next = NULL;
        (*queue)->first = aux;
        (*queue)->size = 1;
        return;


    }

    else
    {
        if(priority<=(*queue)->first->priority)//先和第一节点比较,如果权值比第一节点小,就插入当第一节点
        {
            aux->next = (*queue)->first;
            (*queue)->first = aux;
            (*queue)->size++;
            return ;
        }
        else//按从小到大插入节点
        {
            pQueueNode *iterator = (*queue)->first;
            while(iterator->next!=NULL)//为什么先比较第一节点,再用first->next比较,这样避免单求前驱节点,而且队列没有空的头结点,所以头结点要先比较
            {
                if(priority<=iterator->next->priority)//插入terator->next节点之前
                {
                    aux->next = iterator->next;
                    iterator->next = aux;
                    (*queue)->size++;
                    return ;
                }
                iterator = iterator->next;
            }
            if(iterator->next == NULL)//说明要插入的节点最小,则插入队列末尾
            {
                aux->next = NULL;
                iterator->next = aux;
                (*queue)->size++;
                return ;
            }
        }
    }
}

获取队列节点的函数

htNode* getPQueue(pQueue **queue)
{
    htNode* returnValue;
    if((*queue)->size > 0)//出队列第一个队列节点的树节点 { returnValue = (*queue)->first->val;
        (*queue)->first = (*queue)->first->next;
        (*queue)->size--;
    }
    else
    {
        printf("\nQueue is empty\n");
    }
    return returnValue;
}

看一下队列节点的定义形式
《对小甲鱼哈弗曼树代码的小修改》

3.buildTable函数讲解
经过上述步骤huffman树已经建立,然后看一下如何建立编码表,即每个字符对应的二进制编码

hlTable * buildTable(htTree *huffmantree)
{
    hlTable *table = (hlTable*)malloc(sizeof(hlTable));
    table->first = NULL;
    table->last = NULL;
    char code[256];//保存编码
    int k=0;
    traverseTree(huffmantree->root,&table,k,code);
    printf("对应编码表为\n");
    print_table(table);
    return table;
}

这里主要是用traverseTree函数来遍历huffman树来生成字符二进制编码

void traverseTree(htNode *treeNode,hlTable **table,int k,char code[256])
{
    if(treeNode->left == NULL && treeNode->right == NULL)//已经到达huffman树叶子节点,
    {
        code[k]='\0';//加上结束符
        hlNode *aux = (hlNode*)malloc(sizeof(hlNode));
        aux->code = (char*)malloc(sizeof(char)*(strlen(code)+1));
        strcpy(aux->code,code);//赋值该字符编码
        aux->symbol = treeNode->symbol;//赋值节点字符,就是叶节点的字符

        aux->next = NULL;
        if((*table)->first == NULL)//插入到编码表里
        {
            (*table)->first=aux;
            (*table)->last = aux;
        }
        else
        {
            (*table)->last->next = aux;
            (*table)->last = aux;
        }
    }
        if(treeNode->left!=NULL)//还没到根节点先查询左孩子
        {
            code[k]='0';//向左走,赋值0
            traverseTree(treeNode->left,table,k+1,code);
        }
        if(treeNode->right!=NULL)//还没到根节点查询右孩子
        {
            code[k]='1';//向右走,赋值1
            traverseTree(treeNode->right,table,k+1,code);
        }

}

这是编码表结构体

typedef struct _hlTable
{
    hlNode *first;  //指向第一个节点
    hlNode *last;//指向第二个节点
}hlTable;

从huffman树根节点开始,用数组code[256]来保存编码,向左走数组当前元素就赋值为0,向右走数组当前元素就赋值为1。大家可以思考一下只有一个数组code,是怎么记录所有字符的二进制编码的。这里我们已经成功建立了编码表。
看一下打印编码表的函数

void print_table(hlTable *table)
{
    hlNode *temp = table->first;
    while(temp!=NULL)
    {
        printf("%c:%s\n",temp->symbol,temp->code);
        temp=temp->next;
    }
    printf("\n");
}

接下来我们通过读入一个字符文档,通过编码表生成二进制形式编码
4.encode函数讲解

char*   encode(hlTable *table,char *stringToEncode)
{
    hlNode *traversal;
    char *str = (char*)malloc(sizeof(char)*256*100);
    memset(str,0,sizeof(char)*256*100);
    printf("读入待编码文档为 :\n%s\n\n编码中.....\n\n编码后结果:\n",stringToEncode);
    for(int i=0;stringToEncode[i]!='\0';i++)//读取整个待解码的字符串
    {
        traversal = table->first;//每次都从字符表中第一个开始找
        while(traversal->symbol !=stringToEncode[i])
        {
            traversal = traversal->next;
        }
        printf("%s",traversal->code);//当在编码表中找到和要编码字符串中相同的字符后,输出哪个字符的编码
        strcat(str,traversal->code);
    }
    return str;
}

最后一个功能就是把二进制文档通过编码表还原成字符
5.decode函数讲解

void decode(htTree *tree,char *stringToDecode)
{
    int index = 0;
    int i;
    htNode *traversal = tree->root;
    printf("\n\n\n读入待译码文档为 :\n%s\n\n译码中.......\n\n译码后文档为 :\n",stringToDecode);
    for(i=0;stringToDecode[i]!='\0';i++)
    {
        if(stringToDecode[i]=='0')//等于0,Huffman树向左走
        {
            traversal = traversal->left;
        }
        else if(stringToDecode[i]=='1')//等于1,Huffman树向右走
        {
            traversal = traversal->right;
        }
        else
        {
            printf("The input string is not coded correctly!\n");
        }
        if(traversal->left == NULL && traversal->right == NULL)//如果已经到了叶节点,则输出叶节点的字符
        {
            printf("%c",traversal->symbol);
            traversal = tree->root;//重新从根节点开始走
            index = i;
        }
    }
    if(traversal!=tree->root)//如果解码所有字符串traversal不是根节点,说明有的编码多余
    {
        printf("第%d到第%d位编码为无效编码",index+1,i);
    }
}

最后看一下运行截图 可以新窗口打开图片
《对小甲鱼哈弗曼树代码的小修改》
日后有空再修改吧

    原文作者:哈夫曼树
    原文地址: https://blog.csdn.net/GilgameshG/article/details/52900807
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞