只是記錄一下自己寫作業的過程,我不是一個程序員,我是一個想進德雲社的閒散人員
一.自己對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);
}
}
最後看一下運行截圖 可以新窗口打開圖片
日後有空再修改吧