Huffman编码与解码_C语言实现

实验目的:

1.        掌握熵编码的原理和方法

2.        掌握霍夫曼编码的原理

3.        了解霍夫曼编码的优缺点

4.        掌握和熟悉C


一、背景知识及相关公式

1.熵,又称为“信息熵”(Entropy)

1.1         在信息论中,熵是信息的度量单位。信息论的创始人Shannon在其著作《通信的数学理论》中提出了建立在概率统计模型上的信息度量。他把信息定义为“用来消除不确定性的东西”。

1.2         一般用符号 H 表示,单位是比特。对于任意一个随机变量 X,它的熵定义如下:

1.3         变量的不确定性越大,熵也就越大。换句话说,了解它所需要的信息量也就越大。

2.  Huffman编码

1.4         Huffman Coding (霍夫曼编码)是一种无失真编码的编码方式,Huffman编码是可变字长编码(VLC)的一种。

1.5         Huffman编码基于信源的概率统计模型,它的基本思路是,出现概率大的信源符号编长码,出现概率小的信源符号编短码,从而使平均码长最小。

1.6         在程序实现中常使用一种叫做树的数据结构实现Huffman编码,由它编出的码是即时码。

3.  Huffman 编码的方法

1.7         统计符号的发生概率;

1.8         把频率按从小到大的顺序排列

1.9         每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,这两个叶子节点不再参与比较,新的根节点参与比较;

1.10      重复3,直到最后得到和为1的根节点;

1.11      将形成的二叉树的左节点标0,右节点标1,把从最上面的根节点到最下面的叶子节点途中遇到的0,1序列串起来,就得到了各个符号的编码。


二、数据结构

1.huffman树节点

typedef struct huffman_node_tag
{
    unsigned char isLeaf;  //是否是叶节点
    unsigned long count;  //字母出现的频率
    struct huffman_node_tag *parent; //父节点指针
 
    union //联合体:如果是叶节点,则只能有symbol,如果是非叶节点,只能有左右孩子指针
    {
        struct
        {
            struct huffman_node_tag *zero, *one;  //左右孩子指针
        };
        unsigned char symbol; //该节点对应的字母
    };
} huffman_node;

2.huffman码字节点

typedef struct huffman_code_tag
{
    //以位为单位的码字长度
unsigned long numbits;
 
/*码字(二进制):码字的第1位位于bits[0]的第1位;
                  码字的第2位位于bits[0]的第2位
                      ……
                  码字的第8位位于bits[0]的第8位
                  码字的第9位位于bits[1]的第1位 */
    unsigned char *bits;
} huffman_code;


3.输出缓冲结构体

typedef struct buf_cache_tag /*内存编码时,结构体存放输出内存及缓存的指针*/
{
//cache:缓存作用
//如果待存入数据大小合适,则放入*cache;
/*如果待存入数据与*cache中原有数据大小之和超出cache_len,则将原有数据与待存入数据一起放入输出内存*pbufout,最后将*cache内容清空*/
unsigned char *cache;
 
//缓存区*cache的大小,本程序将其设为1024字节
unsigned int cache_len;
 
//缓冲区*cache当前已缓存数据的大小(当前已缓存大小)
unsigned int cache_cur;
 
//最终所有输出数据存放的内存区域,即输出内存的二级指针
unsigned char **pbufout;
 
//最终所有输出数据的大小之和,即*pbufout所指向的内存大小
    unsigned int *pbufoutlen;
} buf_cache;

 

思考

         为什么使用pbufout二级指针?输出内存**pbufout是通过malloc后多次realloc获得,malloc后内存地址一定会变,realloc后内存地址有时会变有时不变(MSDN上说,*realloc returns a void pointer to the reallocated (and possiblymoved) memory block.),所以输出内存地址(指向输出内存的指针)是不断变化的,即指针内容会发生改变,因此要想通过函数改变指针内容,并使该内容可以被函数外环境使用,只能操作二级指针。

         为什么使用pbufoutlen指针?要想通过函数改变输出内存大小的值,并使该内容可以被函数外环境使用,只能操作指针。


三、主函数分析

1.getopt()分析命令行参数

 

头文件:#include<unistd.h>   (unix standard header缩写  unix 标准头文件)

 

原型:

int getopt(int argc,char * const argv[],const char * optstring);

参数argc和argv是由main()传递的参数个数和内容。参数optstring 则代表预处理选项字符串。

 

什么是选项?什么是参数?

字符串optstring可以下列元素

1.单个字符,表示选项。

2.单个字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。该参数的指针赋给optarg。

3.单个字符后跟两个冒号,表示该选项后必须跟一个参数。参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给optarg。

 

 

调用原理:

调用一次,返回一个选项。如果选项字符串里的字母后接着冒号“:”,则表示还有相关的参数,char* optarg指向该参数。在命令行选项参数再也检查不到optstring中包含的选项时,返回-1,同时optind储存第一个不包含选项的命令行参数。

 

相关变量:

optarg是char*型变量,会指向此额外参数。

 

返回值:

getopt()每次调用会逐次返回命令行中符合的选项。

 当没有参数的最后的一次调用时,getopt()将返回-1。

 当解析到一个不在optstring里面的参数,或者一个必选值参数不带值时,返回'?'。

 

注意三点:

(1). 不带值的参数可以连写,象1和a是不带值的参数,它们可以-1-a分开写,也可以-1a或-a1连写。

(2). 参数不分先后顺序,'-1a -ccvalue -ddvalue'和'-d -c cvalue -a1'的解析结果是一样的。

(3). 要注意可选值的参数的值与参数之间不能有空格,必须写成-ddvalue这样的格式,如果写成-d dvalue这样的格式就会解析错误。


本程序应用:


(1).文件编码:


getopt处理以'-’开头的命令行参数,如图optstring=”i:o:cdhvm”,命令行为huff_run.exe–i test.doc –o 1.huf –c。在这个命令行参数中,-i、-o和-c就是选项元素,去掉'-',i、o和c就是选项。test.doc是i的参数,1.huf是o的参数。其中顺序可以改变,增加了程序的灵活性。


(2).文件解码:


(3).内存编码:


(4).内存解码:



2.main()函数分析

int main(int argc, char** argv)  //argc:命令行参数个数
//argv:字符指针数组:命令行参数
{
    char memory = 0;  //memory缺省值为0,即默认为文件编解码,而非内存
    char compress = 1;  //compress缺省值为1,即默认为编码
    int opt;  //接收getopt()返回值,为选项或-1
const char *file_in = NULL, *file_out = NULL;  //输入输出文件路径及文件名
                                       //缺省目录则表明为当前目录
    FILE *in = stdin;  //缺省值为标准输入文件
    FILE *out = stdout;  //缺省值为标准输出文件

    //得到命令行参数
    while((opt = getopt(argc, argv, "i:o:cdhvm")) != -1)
    {
        switch(opt)  //opt为iocdhvm字母之一
        {
        case 'i':   //-i后接输入文件
            file_in = optarg;  //optarg为选项参数缩写,该变量存放参数
//注意:optarg无须另设
            break;
        case 'o':  //-o后接输出文件
            file_out = optarg;
            break;
        case 'c':  //-c表明程序功能为文件压缩
            compress = 1;
            break;
        case 'd':  //-d表明程序功能为文件解压
            compress = 0;
            break;
        case 'h':  //-h表明需要显示help使用方法:
               /*fputs("Usage: huffcode [-i<input file>] [-o<output file>] [-d|-c]\n"
		         "-i - input file (default is standard input)\n"
		         "-o - output file (default is standard output)\n"
		         "-d - decompress\n"
		         "-c - compress (default)\n"
  "-m - read file into memory, compress, then write to file 
(not default)\n", out);*/
            usage(stdout);  //输出上述信息到屏幕上
            return 0;
        case 'v':
            version(stdout);  //输出版本版权信息
            return 0;
        case 'm':   //-m表明为内存编码或内存解码
            memory = 1;
            break;
        default:   //如果是其他情况,则将使用方法信息送到标准错误文件
            usage(stderr);
            return 1;
        }
    }

    //如果给出输入文件,则打开该文件
    if(file_in)
    {
        in = fopen(file_in, "rb");
        if(!in)
        {
            fprintf(stderr,"Can't open input file '%s': %s\n",file_in, strerror(errno));   //strerror(errono);返回值为错误的字符串信息
            return 1;
        }
}

	//如果输出文件名给出,则创建该文件
    if(file_out)
    {
        out = fopen(file_out, "wb");
        if(!out)
        {
            fprintf(stderr,
                    "Can't open output file '%s': %s\n", file_out, strerror(errno));
            return 1;
        }
    }
	// memory为1时,说明是内存编解码
    if(memory)
    {
        return compress ?   //compress为1时内存编码,为0时内存解码
            memory_encode_file(in, out) : memory_decode_file(in, out);
    }
	//若执行到此,说明是文件编解码
    return compress ?   //compress为1时文件编码,为0时文件解码

        huffman_encode_file(in, out) : huffman_decode_file(in, out);
}

3.  errno变量和strerror()函数

 

3.1  errno:(Error No. 的缩写)

概念:是一个int型变量------记录系统最后一次错误代码。

头文件:#include<errno.h>

部分输出错误原因定义:

#define EPERM 1  //Operation not permitted

#define ENOENT 2  //No such file or directory

#define ESRCH 3  //No such process

……



3.2  strerror():

函数作用:获取系统错误信息,将单纯的标号转为字符串描述。

头文件:#include<string.h>

补充:常配合errno使用,即strerror(errno)

举例:



四、huffman_encode_file

1.文件编码流程

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值