只是记录一下自己写作业的过程,我不是一个程序员,我是一个想进德云社的闲散人员
一.自己对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);
}
}
最后看一下运行截图 可以新窗口打开图片
日后有空再修改吧