⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 huffman_arithmetic.c

📁 自己用C编写的Huffman编码通信系统应用
💻 C
字号:
#include "huffman.h"
//2006-11-04
/*利用哈夫曼树很容易求出给定字符集及其概率(或频度)分布的最佳不等长编码。哈夫曼编码正是一种应用广泛
  且非常有效的数据压缩技术。该技术一般可将数据文件压缩掉20%至90%,其压缩效率取决于被压缩文件
  的特征。
*/
/*由哈夫曼树求得编码为最优异字头码的原因:
①每个叶子字符ci的码长恰为从根到该叶子的路径长度li,平均码长(或文件总长)又是二叉树的带权路径长度WPL。而哈夫曼树是WPL最小的二叉树,因此编码的平均码长(或文件总长)亦最小。
②树中没有一片叶子是另一叶子的祖先,每片叶子对应的编码就不可能是其它叶子编码的前缀。即上述编码是二进制的异字头码。
*/

/*-------------------------------------------------------------------------------------*/
//在函数InputWeightData()中调用函数characters_analysis()从而求出了N和M的大小。
void CreateHuffmanTree(HuffmanTree T,const char* const FILENAME) //构造哈夫曼树;T[M-1]为其根结点
{
	int i;
	int p1;
	int p2;    

	InitHuffmanTree(T); //将T初始化,T有M个结点,即T是有M个元素的数组。
	InputWeightData(T,FILENAME);//输入N个叶子的权值及数据至T[0..N-1]的weight域及data域
	for(i = N;i < M;++i){ //共进行N-1次合并,新结点依次存于T[i]中
		SelectMin(T,i - 1,&p1,&p2); //在T[0..i-1]中选择两个权最小的根结点,其序号分别为p1和p2
        T[p1].parent = i; //数组下标;其值>=0。
		T[p2].parent = i;        
        T[i].lchild = p1; //最小权的根结点是新结点的左孩子
		T[i].rchild = p2; //次小权的根结点是新结点的右孩子
		T[i].weight = T[p1].weight + T[p2].weight;
	} 
}

/*-------------------------------------------------------------------------------------*/
//哈夫曼编码算法——由哈夫曼树T求得字符集的"哈夫曼编码表H"
void SetHuffmanEncodeTable(HuffmanTree T,HuffmanCodeTable H) //根据哈夫曼树T求哈夫曼编码表H
{
	int child;
	int parent; // c和p分别指示T中孩子和双亲的位置
	int i; 
    char code[N1 + 1]; //临时存放编码,数组大小为N1 + 1。
	int start; //指示编码在code中的起始位置

	code[N1] = '\0'; //编码结束符
	for(i = 0;i < N;++i){ //依次求N个叶子结点T[i]的编码
		H[i].ch = T[i].data;//读入叶子T[i]对应的字符
		start = N1; //编码起始位置的初值
		child = i; //从叶子T[i]开始上溯
		while((parent = T[child].parent) >= 0){//直至上溯到T[c]是树根为止,树根时parent为-1。
			code[--start] = (T[parent].lchild == child) ? '0' : '1';//用一个三目运算符表示
//若T[child]是T[parent]的左孩子,则生成代码0;否则生成代码1
            child = parent; //继续上溯
		}
		strcpy(H[i].bits,&code[start]); //复制编码位串,在此好好理解字符串拷贝
/*&code[start]表示是以字符串code的从start位置开始到结束的一个code的字符子串。因为start是字符串
  开始的有效位置,所以从此地方开始拷贝字符串。*/
	}
}

/*-------------------------------------------------------------------------------*/
//哈夫曼编码——对原始数据文件"DATAFILE"的编码:
//对数据文件中的1个字符进行编码
char HuffmanEncode(HuffmanCodeTable H,FILE* fp1,FILE* fp2)
{
	int i = 0;
	char ch;
	char code[N1 + 1]; //临时存放编码	

	ch = ReadACharFromFile(fp1); //从原始数据文件中读取一个字符
	if(ch == '\0') //当读到文件尾标记EOF时返回了标记'\0'
		return ch; //返回'\0'标记读到文件尾标记EOF

	FindChar(H,ch,&i); //在哈夫曼编码表H中找到字符ch的位置i
	if(ch == H[i].ch)
		strcpy(code,H[i].bits); //编码结果存在code中
	WriteStringsToFile(fp2,code);//把字符串code写入编码文件保存
	return '!'; //返回非'\0'
}

/*----------------------------------------------------------------------------------*/
//哈夫曼译码——对已编码文件"ENCODEFILE"的译码:
/*算法中可以使用T[i].lchild是否为-1来判定T[i]是否为叶子结点,是因为哈夫曼树是严格二叉树,树中
  没有度数为1的分支结点。*/
//哈夫曼树的根结点为T[M-1]。 
//对1个字符对应的编码序列进行译码
char HuffmanDecode(HuffmanTree T,HuffmanCodeTable H,FILE* fp1,FILE* fp2)
{
	int i;
	char ch;
	char ch1; //临时存储所译码的对应字符集中的字符。

	i = M -1; //指向树的根结点。此地方要用M,不能用M1,注意!!!因为Huffman树的结点总数是M。
	while(1){
		ch = ReadACharFromFile(fp1); //从编码文件中读取一个二进制码'0'或'1'字符
		if(ch == '\0')  //当读到文件尾标记EOF时返回了标记'\0'
			return ch; //返回'\0'标记读到文件尾标记EOF

		if(ch == '0')
			i = T[i].lchild; //转向左孩子
		else
			i = T[i].rchild; //转向右孩子
		if(T[i].lchild == -1){ //则已到叶子结点
			ch1 = H[i].ch;//对应哈夫曼码表中的字符
			WriteACharToFile(ch1,fp2); //把译码结果写入译码文件保存
			break; //结束此次译码
		}
	}
    return '!'; //返回非'\0'
}

/*--------------------------------------------------------------------------------------------*/
void InitHuffmanTree(HuffmanTree T) //将T初始化,T有M个结点,即T是有M个元素的数组。
{
	int i;

/*初始定义的数组大小为M1;其有效数据元素为M个。M是在程序中所求;M1是一个常量,以声明数组的
  大小(编译时就要确定数组的大小)。*/
	for(i = 0;i < M1;++i){ //这里M的值还没有求出来,故对整个初始数组进行初始化
		T[i].data = '\0';
		T[i].weight = 0.0;
		T[i].lchild = -1;
		T[i].rchild = -1;
		T[i].parent = -1;
	}
}	

/*--------------------------------------------------------------------------------------*/
//输入N个叶子的权值及数据至T[0..N-1]的weight域及data域
//由分析原始数据文件后得到字符集F的字符个数N。在函数characters_analysis()中求出了N和M。
void InputWeightData(HuffmanTree T,const char* const FILENAME) 
{
	int i;
    StrSetFrequencyTable F; //字符集的频率(频度)表F(数组),有N个元素(对应N个字符)

    characters_analysis(F,FILENAME);//分析数据文本文件的字符构成及各字符的频率(频度)
//到此已求出了N和M的大小。
	for(i = 0;i < N;++i){ //这里要用N,表示N个叶子结点。不能用N1。注意!!!
		T[i].data = F[i].data;
		T[i].weight = F[i].weight;
	}
}

/*---------------------------------------------------------------------------------*/
void SelectMin(HuffmanTree T,int i,int* p1,int* p2)//在T[0..i]中选择两个权最小的根结点,由p1,p2返回其位置
{
	int minimum1; //最小值位置标记
	int minimum2; //次小值位置标记
	int j;
	int flag1;
	int flag2;
	int FLAG;

	FLAG = 0;
	for(j = 0;j <= i;++j){//在当前的(i+1)个结点中选取两个权值最小的根结点
		while(T[j].parent != -1)//找一个树根;为-1时即为求解问题时森林中某孤立树的树根。
			++j; 		
		flag1 = j;
		if(FLAG == 0){//第一次要找两个孤立树的树根,以后每次只需找一个;以后不再执行此代码段。
			while(T[++j].parent != -1)//找下一个孤立树的树根;
				continue;
			flag2 = j;
			if(T[flag1].weight < T[flag2].weight){
				minimum1 = flag1;
				minimum2 = flag2;
				FLAG = 1;
				continue;//继续找下一个孤立树的树根;进行下一次for循环
			}
		}
		if(T[flag1].weight < T[minimum1].weight){
            minimum2 = minimum1; //修正次小值
			minimum1 = flag1; //修正最小值
		}			
		else if(T[flag1].weight < T[minimum2].weight)//只需修正次小值
			minimum2 = flag1; 			
	}
	*p1 = minimum1;
	*p2 = minimum2;
}

/*------------------------------------------------------------------------------*/
void FindChar(HuffmanCodeTable H,char ch,int* ptr_i)
{
	int j;

	for(j = 0;j < N;++j){
		if(H[j].ch == ch){
			*ptr_i = j;
			break;
		}
	}
}

/*-------------------------------------------------------------------------------*/
/*① 初始森林中的n棵二叉树,每棵树有一个孤立的结点,它们既是根,又是叶子
  ② n个叶子的哈夫曼树要经过n-1次合并,产生n-1个新结点。最终求得的哈夫曼树中共有2n-1个结点。
  ③ 哈夫曼树是严格的二叉树,没有度数为1的分支结点。

哈夫曼算法的简要描述:
     在上述存储结构上实现的哈夫曼算法可大致描述为(设T的类型为HuffmanTree):
(1)初始化
将T[0..m-1]中2n-1个结点里的三个指针均置为空(即置为-1,因为数组下标从0开始),权值置为0.0,
数据值置为'0'。
(2)输入
读人n个叶子的权值存于向量的前n个分量(即T[0..n-1])中。它们是初始森林中n个孤立的根结点上的权值。
(3)合并
对森林中的树共进行n-1次合并,所产生的新结点依次放人向量T的第i个分量中(n≤i≤m-1)。每次合并分两步:
①在当前森林T[0..i-1]的所有结点中,选取权最小和次小的两个根结点[p1]和T[p2]作为合并对象,
  这里0≤p1,p2≤i-1。
②将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树,新树的根是新结点T[i]。具体操作:
 将T[p1]和T[p2]的parent置为i,
 将T[i]的lchild和rchild分别置为p1和p2
 新结点T[i]的权值置为T[p1]和T[p2]的权值之和。
注意:合并后T[pl]和T[p2]在当前森林中已不再是根,因为它们的双亲指针均已指向了T[i],所以下一次合并
      时不会被选中为合并对象。
*/

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -