一.实验原理
1.Huffman编码
1)Huffman Coding(霍夫曼编码)是一种无失真编码的编码方式,Huffman编码是可编长编码(VLC)的一种。
2)Huffman编码基于信源的概率统计模型,它的基本思路是:出现概率大的信源符号编短码,出现概率小的编长码。从而实现平均码长最小。
3)在程序实现中常使用一种叫做树的数据结构实现Huffman编码,由它编出的码是即时码。
2.Huffman编码的方法
2.1 统计符号的发生概率;
2.2 把频率从小到大的顺序排列
2.3 每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,这两个叶子节点不再参与比较,新的根节点参与比较;
2.4 重复3,知道最后得到和为1的根节点。
2.5 将形成的二叉树的左节点标为0,右节点标为1,把从最上面的根节点到最下面的叶子节点途中遇到的0,1序列串起来,就得到了各个符号的编码。
3.实验中遇到的递归式遍历方法
先序遍历
中序遍历
后序遍历
综上,Huffman编码结构中, build_symbol_encoder()的函数,实现功能为:对存在的每个字符计算码字。该函数使用的遍历方法应该为递归”中序遍历”;假设已经遍历到最左边的叶节点,由if判断到叶节点的子节点为空,故Return。此时输出一个码子,然后进入下一个右节点。其对节点的遍历顺序应为,中序遍历。仅单纯观察代码易混淆为先序遍历。
if(subtree == NULL) return; if(subtree->isLeaf) (*pSF)[subtree->symbol] = new_code(subtree); else { //如果不是叶字节点,就需要往下遍历 build_symbol_encoder(subtree->zero, pSF);//先遍历其左子树 build_symbol_encoder(subtree->one, pSF);//再遍历其右子树 }
4.实验中所用到的数据结构分析
typedef struct huffman_node_tag
{
unsigned char isLeaf;//判断该节点是否是叶节点的标志
unsigned long count;//信源中出现频数
struct huffman_node_tag *parent;//父节点指针
union
{
struct
{
struct huffman_node_tag *zero, *one;
};
unsigned char symbol;
};
} huffman_node;
huffman_node节点中的有union数据结构,这么设计的优点是节约了空间。当节点为叶节点时,必定不包含子节点指针,当节点非叶节点时,必存在子节点指针。
typedef struct huffman_code_tag
{
/* The length of this code in bits. */
unsigned long numbits;
/* The bits that make up this code. The first
bit is at position 0 in bits[0]. The second
bit is at position 1 in bits[0]. The eighth
bit is at position 7 in bits[0]. The ninth
bit is at position 0 in bits[1]. */
unsigned char *bits;
} huffman_code;
huffman_code是Huffman码表数据结构,用以储存符号对应的码字。
typedef struct huffman_info_tag
{
double freq;//符号出现频率
unsigned long len_code;//符号对应的码长
unsigned char *code;//符号对应的码字
}huffman_info;
huffman_info数据结构,用于储存每个符号的符号频率、码长、码字信息。用于压缩后统计压缩数据,计算压缩效率,以及验证压缩正确性。
- 分别定义三个数组,数组中储存的是指向结构体的指针。
#define MAX_SYMBOLS 256
typedef huffman_node* SymbolFrequencies[MAX_SYMBOLS];
typedef huffman_code* SymbolEncoder[MAX_SYMBOLS];
//cai add
typedef huffman_info* SymbolInfo[MAX_SYMBOLS];
举例,SymbolFrequencies[MAX_SYMBOLS]储存着256个指向huffman_node这个结构体的指针。
5.getopt()函数简单分析
传入的参数,例如:"-i input.rgb -o output.rgb -c" "i"与“o”在下列注释中称为key,其对应的“input.rgb”称为value。
以及调用getopt时,opt = getopt(argc, argv, "i:o:cdhvm")。"i:o:cdhvm"在下列注释中称为规范字符串
extern char *optarg;
extern int optind;
extern int opterr;
int opterr = 1, /* if error message should be printed */
optind = 1, /* index into parent argv vector */
optopt, /* character checked for validity */
optreset; /* reset getopt */
char *optarg; /* argument associated with option */
#define BADCH (int)'?'
#define BADARG (int)':'
#define EMSG ""
int getopt(int nargc, char * const *nargv, const char* ostr)
{
static char *place = EMSG; /* place向量先置为空 */
char *oli; /* 用于指向 “规范字符串”的指针 */
if (optreset || !*place) { /* 如果optreset为1,或者place指向空,都会进入这个判断 */
optreset = 0; /*进入后,将optreset重置为0*/
if (optind >= nargc || *(place = nargv[optind]) != '-') { /*判断optind是否比总argc数大,以及argv[opind]的第一个字符是否为'-' .*/
place = EMSG; /*若满足其上任一项,说明字符读取到了最后一步,或者输入有误*/
return (EOF); /*将place指向 argv[optind]*/
}
if (place[1] && *++place == '-') {