huffman编码与解码

一.相关知识及公式

1.信息熵

熵是信息的度量单位,香农将信息定义为“用来消除不确定性的东西”。

信息熵用H表示,单位是比特/符号,对任意一个随机变量X,熵定义如下:


变量的不确定性越大,信息熵越大,即了解它需要的信息越多

2.huffman编码:一种无失真的变长编码方式(VLC)

基于信源的概率统计模型,大概率的信源符号编短码,小概率的信源符号编长码。

使用二叉树结构,编出的码是即时码。

3.huffman码编码方法

1.将文件以ASCII字符流的形式读入,统计每个符号发生的概率

2.将所有文件中出现过的字 符按照频率 从小到大的顺序排列

3.每一次选出最小的两个值, 作为二叉树的两个叶子节点,将和作为它们的根节点,两个叶子结点不参与比较,根节点参与比较

4.重复步骤三,直到得到和是1的根节点

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

二.实验过程

使用二叉树数据结构实现编码

1.huffman结点结构

typedef struct huffman_node_tag
{
	unsigned char isLeaf;//是否为叶结点
	unsigned long count;//信源出现频数
	struct huffman_node_tag *parent;//父结点(结构体)指针
	//如果不是树叶,此项为左,右结点的指针,否则为某个信源符号
	/*union:与struct相似,维护足够的空间来放置多个数据成员的一种,
	同一时间只能存储其中的一个数据成员,而不是为每一个数据成员都配置空间*/
	union
	{
		struct
		{
			struct huffman_node_tag *zero, *one;//zero:左结点,one:右结点 指针
		};
		unsigned char symbol;//信源符号
	};
} huffman_node;
2.huffman码字结点
typedef struct huffman_code_tag
{
	/* 码字长度numbits,用来记录从leaf--->root一共走了多少步. 
	及叶子节点对应字符的编码长度*/
	unsigned long numbits;

	/* bits用来存储编码,以Byte为单位,
	而编码是以bit为单位的, 所以需要根据 numbits 去从 bits 里提取出前numbits 个bit。
	 numbits 和 bits是有关系的, bits一定不可能超过 numbits/8 */
	unsigned char *bits;//位
} huffman_code;

具体的编码流程:


编码函数:

int huffman_encode_file(FILE *in, FILE *out, FILE *out_Table) 
{
	SymbolFrequencies sf;//用于存储字符频率
	SymbolEncoder *se;//存放编码的码字
	huffman_node *root = NULL;//根节点
	int rc;
	unsigned int symbol_count;//字符频数
    //
	huffman_stat hs;//输出数据结构体
	//

	/* 计算输入文件中字符出现的总频数 */
	symbol_count = get_symbol_frequencies(&sf, in); //

	// 依据统计到的频数计算字符对应的频率
    huffST_getSymFrequencies(&sf,&hs,symbol_count);
    //

	/* Build an optimal table from the symbolCount. */
	//创建huffman码树
	se = calculate_huffman_codes(&sf);
	root = sf[0];//根节点
    
	// 获得码字并输出到列表
	huffST_getcodeword(se, &hs);
	output_huffman_statistics(&hs,out_Table);
	//

	/* Scan the file again and, using the table
	   previously built, encode it into the output file. */
	//将文件内部的指针重新指向输入文件流的开头
	rewind(in);
	//将字符频数、码表等写入到输出列表
	rc = write_code_table(out, se, symbol_count);//写入成功返回0
	//写入成功后进行第二次扫描,对源文件进行编码输出
	if(rc == 0)
		rc = do_file_encode(in, out, se);

	/* Free the Huffman tree and the code. */
	free_huffman_tree(root);
	free_encoder(se);
	return rc;
}

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));
			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;
        }
    }


2:创建用于存储字符频率和码字的结构体:

typedef huffman_node* SymbolFrequencies[MAX_SYMBOLS];
typedef huffman_code* SymbolEncoder[MAX_SYMBOLS];
其中:
#define MAX_SYMBOLS 256
用于存储输出数据的结构体:

//huffman statistics huffman编码的统计结果
typedef struct huffman_statistics_result
{
	float freq[256];
	unsigned long numbits[256];
	unsigned char bits[256][100];
}huffman_stat;
3:统计字符出现的频数并创建结点:

//扫描FILE对象,计算FILE对象内的各个字符出现的频率
static unsigned int
get_symbol_frequencies(SymbolFrequencies *pSF, FILE *in)
{
    int c;
    unsigned int total_count = 0;// FILE对象内的字符总数
    
    /* 初始化频率为0. */
    init_frequencies(pSF);
    
    /* Count the frequency of each symbol in the input file. */
    while((c = fgetc(in)) != EOF)//扫描输入文件,EOF:即-1,表示文件结束
    {
        unsigned char uc = c;
        //(*pSF)[uc]表示一个字符uc出现的频次 如果这个字符没有出现过则为这个字符建立一个叶子
        if(!(*pSF)[uc])
            (*pSF)[uc] = new_leaf_node(uc);
        //uc字符huffman_node的count自加
        ++(*pSF)[uc]->count;
        ++total_count;//总数也自加
    }

    return total_count;//返回值是字符的总数
}

叶子结点:

new_leaf_node(unsigned char symbol)
{
	huffman_node *p = (huffman_node*)malloc(sizeof(huffman_node));
	p->isLeaf = 1;//1代表是叶子结点
	p->symbol = symbol;// symbol : 该叶子节点表示的字符
	p->count = 0;
	p->parent = 0;
	return p;
}

4.依据统计的频数计算字符相应的频率

int huffST_getSymFrequencies(SymbolFrequencies *SF, huffman_stat *st,int total_count)
{
	int i,count =0;
	for(i = 0; i < MAX_SYMBOLS; ++i)
	{	
		if((*SF)[i])//字符出现的频率不为0
		{
			//计算该字符出现的频率存储在结构体huffman_stat中
			st->freq[i]=(float)(*SF)[i]->count/total_count;
			count+=(*SF)[i]->count;//所有出现的字符的次数之和,目的是为了遍历
		}
		else 
		{
			st->freq[i]= 0;//该字符出现的频率为0
		}
	}
	if(count==total_count)//遍历了整个文件中的所有字符
		return 1;
	else
		return 0;
}
5.按照字符频率从小到大的顺序建立码树

//建立huffman码树
static SymbolEncoder*
calculate_huffman_codes(SymbolFrequencies * pSF)
{
	unsigned int i = 0;
	unsigned int n = 0;
	huffman_node *m1 = NULL, *m2 = NULL;//m1:左结点 m2:右结点
	SymbolEncoder *pSE = NULL;//存放码字的指针
	
#if 1
	printf("BEFORE SORT\n");
	print_freqs(pSF);   //演示堆栈的使用
#endif

	/* Sort the symbol frequency array by ascending frequency.
	以symbol频率为依据做升序排列*/
	qsort((*pSF), MAX_SYMBOLS, sizeof((*pSF)[0]), SFComp);  
#if 1	
	printf("AFTER SORT\n");
	print_freqs(pSF);
#endif

	/* Get the number of symbols. */
	//计算当前待编码文件中信源符号的总数
	for(n = 0; n < MAX_SYMBOLS && (*pSF)[n]; ++n)//&&:逻辑与
		;

	/*Construct a Huffman tree.
	 * Note that this implementation uses a simple count instead of probability.
	 */
	//使用计数而不是概率
	for(i = 0; i < n - 1; ++i)//循环n-1次
	{
		/* m1,m2设置为频率最低的信源符号 */
			m1 = (*pSF)[0];
		m2 = (*pSF)[1];

		/* 将m1,m2合并成一个huffman结点(非叶结点),存到数组中,
		左右结点分别是m1,m2,新的结点的频数是m1,m2频数之和 */
		(*pSF)[0] = m1->parent = m2->parent =//将此非叶结点设置成左右结点的父结点
			new_nonleaf_node(m1->count + m2->count, m1, m2);
		(*pSF)[1] = NULL;//用(*pSF)[0]指向该新建结点,而将(*pSF)[1](下一个结点)置空
		
		/* 重新排序*/
		qsort((*pSF), n, sizeof((*pSF)[0]), SFComp);
	}

	//给码字结点指针数组分配内存空间 
	pSE = (SymbolEncoder*)malloc(sizeof(SymbolEncoder));
	memset(pSE, 0, sizeof(SymbolEncoder));//初始化该数组
	build_symbol_encoder((*pSF)[0], pSE);//以此结点为root建立huffman树
	return pSE;//返回码字指针
}

主要依靠函数qsort实现;

qsort函数原型:void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*));

参数1:待排序数组首地址

参数2:待

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值