對小甲魚哈弗曼樹代碼的小修改

只是記錄一下自己寫作業的過程,我不是一個程序員,我是一個想進德雲社的閒散人員
一.自己對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);
    }
}

最後看一下運行截圖 可以新窗口打開圖片
這裏寫圖片描述
日後有空再修改吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章