Huffuman Coding(哈夫曼编码)

哈夫曼编码:

假设要给一偏文章进行编码,文章由英文组成,对于全文来说,我们可以统计得到这篇文章各个字母出现的个数,个数大的字母意味着它在文章中出现的频率更高,对所有的字母都用相同的空间大小编码的话会产生一定的空间浪费。哈夫曼编码使得高频率出现的字母用更短码值表示,低频率出现的字母用更长的码值表示,可以缩小整篇文章的空间占用,达到压缩的目的。把字母看做编码用的各个编码数值同理。

哈夫曼树定义:

给定n个自带权值的节点构造一个二叉树,若带权路径长度最小,称这样的树为哈夫曼树或者最优二叉树。哈弗曼树是权值最小的二叉树,权值越大的节点离根节点越近。

基本概念:

  1. 路径和路径长度:在一棵树中,从一个节点往下可以到达的孩子或者孙子节点之间的通路,成为路径。通路中分支路径的个数成为路径长度。
  2. 节点的权与带权路径长度:若将树中节点赋予一个有意义的数值,则这个数值成为这个节点的权。节点的带权路径长度为节点当前权值乘上路径长度。
  3. 树的带权路径长度: 树的带权路径长度为所有叶节点的带权路径长度之和,几位WPL。

哈夫曼树的构造:

  1. 把n个节点看做n棵二叉树,此时每棵树只有一个节点
  2. 在森林中选择两个权值最小的树进行合并,形成新的二叉树,该树的根节点的权值为合并的左右树的权值之和
  3. 从森林中删去合并的两棵树,并加入合并后的那棵新树
  4. 重复2、3步直到森林中只剩一棵树结束,哈夫曼树构造完成。

哈弗曼编码:

先统计带压缩文件的各个节点的权值,生成一个哈夫曼树;根据单词的各个编码字符,从节点开始,找到这个节点的parent节点在比较原节点是父节点的左孩子还是右孩子,一般规定是左孩子为0,右孩子为1,比较到根节点为止,得到的编码是最终编码的倒序,进而得到每个节点的哈夫曼编码。之后结点的个数,符号,以及编码写在哈夫曼压缩文件头部,供文件解压的时候使用;之后开始读入压缩文件,将已有的字符对应的哈夫曼编码写入待输出二进制文件完成压缩。哈夫曼解码的时候根据文件头的信息生成哈夫曼树,因为哈夫曼编码为前缀编码,之后从根节点开始将编码转化成一个个字符(哈夫曼树的叶子节点),以此类推,解压完成。

源程序代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define maxsize 20001

typedef struct BTnode{
    int weight;//权重 
    int parent;//父节点 
    int lchild;//子节点 
    int rchild;
    unsigned char a;
}BTnode;

typedef struct CharNode{
    char bit[maxsize];
    unsigned char a;
    int weight; 
}CharNode;//字符节点(存储权重与编码) 

BTnode BT[maxsize];
CharNode Dictionary[maxsize];
int root;
int node_num;
long File_Point;

void get_weight(char* input)//读取未压缩文件进行的权重生成 
{
    printf("字符权重计算中...\n") ;
    unsigned char temp, pre;
    FILE *fp;
    fp = fopen(input, "rb");
    if(fp == NULL)
    {
        printf("Input Error\n");
        exit(0);
    }
    while(!feof(fp))
    {
        fread(&temp, sizeof(unsigned char), 1, fp);
        if(temp == '\0')
            continue;
        BT[temp].weight++;
        BT[temp].a = temp;
        temp = '\0';    
    }
    fclose(fp);
    printf("计算权重已完成\n");
}

void HuffmanTree()//压缩文件过程中源文件生成的霍夫曼树 
{
    printf("正在构建哈夫曼树...\n");
    BTnode t;
    int minweight;
    int lchild, rchild;
    int k=0;
    for(int i = 0; i<maxsize ;i++)//冒泡排序将叶子节点按照从大到小顺序排列 
    {
        for(int j = 0; j<maxsize; j++)
        {
            if(BT[j].weight<BT[j+1].weight)
            {
                t = BT[j+1];
                BT[j+1] = BT[j];
                BT[j] = t;  
            }   
        }
    }
    while(BT[k].weight!=0)
        k++;
    node_num=k;//统计字符个数 
    root = 2*k-2;//k个叶子节点k-1个内部节点 
    for(int i=k; i<2*k-1; i++)//接下去两个for循环取到两个权值最小节点 
    {
         minweight = maxsize;
         lchild = -1;
         rchild = -1;
         for(int j=0; j<i; j++)
         {
            if(BT[j].weight<minweight && BT[j].parent==-1)
            {
                minweight = BT[j].weight;
                lchild = j;
            }
         }
         minweight = maxsize;//将该变量重置为max值以便于后面比较取值 
         BT[lchild].parent = i;
         for(int h=0; h<i; h++)
         {
            if(BT[h].weight<minweight &&BT[h].parent==-1)
            {
                minweight = BT[h].weight;
                rchild = h;
             }
         }
         BT[rchild].parent = i;
         BT[i].weight = BT[lchild].weight + BT[rchild].weight;
         BT[i].lchild = lchild;
         BT[i].rchild = rchild;
    }
    BT[root].parent = maxsize;
    printf("哈夫曼树完成构建\n");
}

void coding(){//哈弗曼编码 
    printf("正在进行哈弗曼编码...\n");
    int ndoe = 0;
    int findp, itself, point;
    int end;
    char temp;
    for(int node = 0; node<=root; node++)
    {
        if(BT[node].a != NULL)
        {
            Dictionary[BT[node].a].a = BT[node].a;
            Dictionary[BT[node].a].weight = BT[node].weight;
            point=0;
            findp = BT[node].parent;
            itself = node;
            while(findp <= root)
            {
                if(BT[findp].lchild == itself)//如果该节点是叶子节点 
                {
                    Dictionary[BT[node].a].bit[point] = '0';
                    point ++;
                }
                else if(BT[findp].rchild == itself)
                {
                    Dictionary[BT[node].a].bit[point] = '1';
                    point ++;
                }
                findp = BT[findp].parent;
                itself = BT[itself].parent;
            }
        }
    }
    for(int i=0; i<maxsize; i++)
    {
        if(strlen(Dictionary[i].bit)!=0)
        {
            for(int j=0; j<strlen(Dictionary[i].bit); j++)
            {
                for(int k=0; k<strlen(Dictionary[i].bit)-j-1; k++)
                {
                    temp = Dictionary[i].bit[k];
                    Dictionary[i].bit[k] = Dictionary[i].bit[k+1];
                    Dictionary[i].bit[k+1] = temp;
                }
            }
        }
    }
    printf("哈弗曼编码已完成\n");
    for(int i=0; i<maxsize; i++)
    {
        if(Dictionary[i].weight!=0)
        {
            printf("符号:%c\t出现频率:%d\t哈夫曼编码%s\n", Dictionary[i].a, Dictionary[i].weight, Dictionary[i].bit);
        }
     } 
}

void Output(char* input)//压缩后的二进制编码生成二进制文件 
{
    unsigned char temp;
    unsigned char output;
    char out_dat[maxsize];
    int num = 0;
    int all = 0;
    FILE *fp, *out;

    printf("生成文件名:");
    scanf("%s", out_dat);
    strcat(out_dat, ".dat");
    printf("生成压缩文件%s\n", out_dat);

    temp = '\0';
    fp = fopen(input, "rb");
    out = fopen(out_dat, "wb"); 
    if(fp == NULL || out == NULL)
    {
        printf("输入文件名错误或建立新文件失败\n");
        exit(0);
    }
    fwrite(&node_num, sizeof(int), 1, out);//将节点个数写入哈弗曼压缩文件 
    for(int i=0; i<node_num; i++)//压缩文件中写入各节点的字符和权重 
    {
        fwrite(&BT[i].a, sizeof(unsigned char), 1, out);
        fwrite(&BT[i].weight, sizeof(int), 1, out);
    }
    while(!feof(fp))
    {
        fread(&temp, sizeof(unsigned char), 1, fp);
// printf("%d:", temp);//555555555555555555555555555555555555555555555555 
// printf("%s\n", Dictionary[temp].bit);
        if(temp == '\0')
            continue;
        all++;
        for(int i=0; i<strlen(Dictionary[temp].bit); i++)
        {
            output <<= 1 ;
            if(Dictionary[temp].bit[i] == '1')
            {
                output +=1;
// printf("1");////////////////////////////////
            }
// else printf("0");/////////////////////////////////
            num++;
            if(num==8)
            { 
                fwrite(&output, sizeof(unsigned char), 1, out);
// printf("\n");//////////////////////////////////
                num = 0;
            }
        }
        temp = '\0';
    }
        if(num!=0){ 
            for(int i=8; i>num; i--)
            {
                output <<=1;
// printf("0");//////////////////////////////////
             } 

            fwrite(&output, sizeof(unsigned char), 1, out);
        }   
        fwrite(&all, sizeof(int), 1, out);
        printf("\n压缩文件成功\n"); 
}

void Form_Huffman(char *input)
{
    printf("生成霍夫曼树...\n");
    FILE *fp;
    BTnode t;
    int num, minweight, all;
    int rchild, lchild;
    unsigned char a;
    int weight;
    fp = fopen(input, "rb");
    fseek(fp, -4, 2);
    fread(&all, sizeof(int), 1, fp);
// printf("%d\n", all);
    fseek(fp, 0, 0);
    fread(&num, sizeof(int), 1, fp);
    for(int i=0; i<num; i++)
    {
        fread(&a, sizeof(unsigned char), 1, fp);
        fread(&weight, sizeof(int), 1, fp);
        BT[i].a = a;
        BT[i].weight = weight;
    }
    root = num;
    for(int i = 0; i<num ;i++)
    {
        for(int j = 0; j<num-1; j++)
        {
            if(BT[j].weight<BT[j+1].weight)
            {
                t = BT[j+1];
                BT[j+1] = BT[j];
                BT[j] = t;  
            }   
        }
    }   
    for(int i=num; i<2*num-1; i++)
    {
         minweight = maxsize;
         lchild = -1;
         rchild = -1;
         for(int j=0; j<i; j++)
         {
            if(BT[j].weight<minweight && BT[j].parent==-1)
            {
                minweight = BT[j].weight;
                lchild = j;
            }
         }
         minweight = maxsize;
         BT[lchild].parent = i;
         for(int h=0; h<i; h++)
         {
            if(BT[h].weight<minweight &&BT[h].parent==-1)
            {
                minweight = BT[h].weight;
                rchild = h;
             }
         }
         BT[rchild].parent = i;
         BT[i].weight = BT[lchild].weight + BT[rchild].weight;
         BT[i].lchild = lchild;
         BT[i].rchild = rchild;
         root = i;
    }
    printf("成功重构霍夫曼树\n"); 

// for(int i=0; i<num; i++)
// {
// printf("%d\n", BT[i].a);
// }

    printf("还原源文件中...\n");
    int root_head;
    char Origin_Code[maxsize];
    unsigned char copy, judge;

    FILE *out;
    printf("输入您想输出的文件名称:");
    scanf("%s", Origin_Code);
    out = fopen(Origin_Code, "w");
    if(out == NULL)
    {
        printf("Input Error\n");
        exit(0);
    }
    root_head = root;
    while(!feof(fp))
    {
        fread(&copy, sizeof(unsigned char), 1, fp);
        for(int i=1; i<=8; i++)
        {
            judge = copy&10000000;
            copy<<=1;
            if(judge>0)
            {
                root_head = BT[root_head].rchild;
// printf("1");///////////////
            }
            else if(judge==0)
            {
                root_head = BT[root_head].lchild;
// printf("0");/////////////////
            }

            if(BT[root_head].a !=NULL)
            {
                fwrite(&BT[root_head].a, sizeof(unsigned char), 1, out);
                all--;
                root_head = root; 
                if(all==0)
                    break;
            }
// if(i==8)
// {
// printf("\n");
// } 
        }
        if(all==0)
            break;  
    }
    printf("还原源文件成功\n");
}

int main()
{
    int num=0; 
    char File_Name[maxsize];
    int choose=0;
    printf("=================================================================\n");
    printf(" 请输入您想要完成的功能:1、压缩文件;2、解压文件;3、退出程序\n");
    printf("\n 注意:(1)压缩文件名带后缀;\n\t(2)压缩文件名默认.dat格式;\n\t(3)带压缩文件名带后缀;\n");
    printf("=================================================================\n");
    printf("输入:");
    scanf("%d", &choose);
    memset(File_Name, 0, sizeof(File_Name));
    root = 0;
    for(int i = 0; i<maxsize; i++)//初始化节点数组 
    {
        BT[i].a = NULL;
        BT[i].lchild=-1;
        BT[i].parent=-1;
        BT[i].rchild =-1;
        BT[i].weight = 0;
    }
    for(int i=0; i<maxsize; i++)
    {
        Dictionary[i].a = NULL;
        Dictionary[i].weight=0;
        Dictionary[i].bit[0] = '\0';
     } 
    switch(choose)
    {
        case 1://压缩文件 
            printf("请输入待压缩文件名:");
            scanf("%s", File_Name);
            get_weight(File_Name);//获取各个字符的权重 
            HuffmanTree();
            coding();
            Output(File_Name);
// for(int i=0; i<maxsize; i++)
// {
// if(Dictionary[i].a!=NULL)
// {
// printf("%d:", Dictionary[i].a);
// printf("%s", Dictionary[i].bit);
// printf("\n");
// }
// }
            break;
        case 2://解压缩文件 
            printf("输出待解压文件名:");
            scanf("%s", File_Name);
            Form_Huffman(File_Name);
            break;
        case 3:
            return 0;
        default :
            choose = 0;
            printf("输入错误请重新输出数据\n");
            break;                      
    }
    return 0;
 } 
点赞