基于Huffman树的文件压缩原理及C语言实现(一)

本文详细介绍了霍夫曼树的基本概念、特点及其构造方法,并通过实例演示了霍夫曼编码的生成过程及解码应用,展示了其作为最优前缀编码在数据压缩中的重要作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是二叉树的“带权”路径长度?

设二叉树具有n个带权值的叶子结点,从根结点到各个叶子结点的路径长度与对应叶子结点权值的乘积之和叫做二叉树的“带权”路径长度。

什么是最优二叉树?

对于一组带有确定权植的叶子结点,带权路径长度最小的二叉树称为最优二叉树。

霍夫曼树的几大特点
  1. Huffman就是一种最优二叉树。
  2. Huffman树肯定最优,不是Huffman树也可能最优树。
  3. 只要权值个数(叶结点数)严格大于1,Huffman树中便不存在度为1的结点。
  4. 权值个数(叶节点数)为n则Huffman树含2n-1个结点。

霍夫曼树并没有它严格的文字定义,霍夫曼树是构造出来的。
也就是说,要说明一棵树是霍夫曼树,那我们就要根据构造霍夫曼树的方法,能够将其构造出来。

如何构造赫夫曼树—赫夫曼算法

  1. 创建n个根结点,权值 {w1,w2,…,wn}, 得森林{T1,T2,…,Tn};
  2. 在森林中选取根结点权值最小的两棵二叉树归并为新二叉树,新二叉树根结点权值为两权值之和;
  3. 将新二叉树加入森林,同时忽略被归并的两棵二叉树,
  4. 重复(2)和(3,至森林只有一棵二叉树。该二叉树就是哈夫曼树。

赫夫曼树对应的编码称为赫夫曼编码,是一种最优前缀编码。

它的构造比较简单:先建好哈夫曼树,左子树的路径标记为0,右子树的路径标记为1。叶节点的哈夫曼编码就是根节点到叶节点的简单路径上的0、1序列。

霍夫曼树的顺序存储,内存模型如下:
霍夫曼树的顺序存储n内存模型

下面给出构建哈夫曼树和哈夫曼编码的c代码:

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>

//函数结果状态代码
#define  TRUE                 1
#define  FALSE                0
#define   OK                  1
#define  ERROR                0
#define  INFEASIBLE           -1
#define  OVERFLOW             -2
typedef  int Status;
typedef unsigned long UInt32;
typedef struct {
	 UInt32 weight;             // 节点权重
	 int parent,lchild,rchild;  // 双亲和左右孩子节点下标
}HTNode,*HuffmanTree,*HTree;

typedef char**HuffmanCode;      //霍夫曼编码类型

/**
 * 从森林中找出两个根结点权重最小的树。
 * 将找到的两棵树的根结点的下标用l和r带回。
 * 入参   T:森林结点数组
 *        n:森林结点数组长度
 */
void Select(HuffmanTree T,int n,int&l,int&r)
{
	//从T的一到n个节点中找出没有结合(即parent=0)的节点中权重weight最小的两个节点的下标;用l,r带回;
	HuffmanTree p=T+1;int a=0,b=0;
	for(int i=1;i<=n;++i,++p){
		if(!p->parent){//找出双亲节点为空的节点;
			if(!a){l=i;a=1;}
			else if(!b){r=i;b=1;}
			else if(p->weight<(T+l)->weight||p->weight<(T+r)->weight){
				if((T+l)->weight<=(T+r)->weight)r=i;
				else l=i;
			}
		}
	}
}

void StrCopy(char*str,const char*c)
{
	char *s=str;
	while(*c!='\0')*s++=*c++;
	*s='\0';
}

/**
 *
 */
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n)
{
	int f,c,start,m1,m2,i,m=2*n-1;HuffmanTree p;
	HT=(HuffmanTree)malloc(sizeof(HTNode)*(m+1));
	if(!HT){printf("内存不足,操作失败!");exit(OVERFLOW);}
	for(p=HT+1,i=1;i<=n;++i,++w,++p){
		p->weight=*w;p->parent=0;p->lchild=0;p->rchild=0;
	}
	for(i=n+1;i<=m;++i,++p){
		p->weight=0;p->parent=0;p->lchild=0;p->rchild=0;
	}
	for(i=n+1,p=HT+i;i<=m;++i,++p){
		Select(HT,i-1,m1,m2);
		p->weight=(HT+m1)->weight+(HT+m2)->weight;
		p->lchild=m1;p->rchild=m2;
		(HT+m1)->parent=i;(HT+m2)->parent=i;
	}

	//for(i=1;i<=m;i++)
	//printf("%d  %d  %d  %d  ",HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);

	HC=(HuffmanCode)malloc(sizeof(char*)*(n+1));
	if(!HC){
		printf("内存不足,操作失败!");exit(OVERFLOW);
	}
	char *cd=(char*)malloc(sizeof(char)*n);cd[n-1]='\0';
	for(i=1;i<=n;++i){
		start=n-1;
  		for(f=HT[i].parent,c=i; f!=0;c=f,f=HT[f].parent)//叶到根逆向求编码
      		if(HT[f].lchild==c)cd[--start]='0';  else cd[--start]='1';
		HC[i]=(char*)malloc(sizeof(char)*(n-start));
		StrCopy(HC[i],cd+start);
	}
	free(cd);
}

/**
 * 输出霍夫曼编码
 */
void PrintHuffmanCode(HuffmanCode C,int* s,int n)
{
	char *p;
	for(int i=1;i<=n;++i){
	    printf("权重:%3d,编码:", s[i-1]);
		p=C[i];
		while(*p!='\0')
		printf("%c",*p++);
		printf("\n");
	}
}

/**
 * 根据霍夫曼树,将霍夫曼编码后的字符进行解码。
 */
Status Rewin(HuffmanTree &T,char*s,int*x,int n,int &m)
{
	//根据huffman树,将s所指向的字符串解码,要求字符串以#结束。用 x 带回解码在数组中的下标, x 从 1 开始。
	HuffmanTree p=T+2*n-1;m=0;
	 while(*s!='#'){
 		 if(*s=='0'&&p->lchild)p=T+p->lchild;
		 else if(*s=='1'&&p->rchild)p=T+p->rchild;
//		 else return FALSE;
		 if(!p->lchild&&!p->rchild){
 			if(*s=='0')*x=(T+p->parent)->lchild;
 			else *x=(T+p->parent)->rchild;
 			x++;p=T+2*n-1;m++;
 		}
 		s++;
 	}
 	return OK;
}

int main()
{
	int s[20]={7 ,19, 2 ,6 ,32, 3, 21, 10}; // 结点对应得权重
	int x[20],n=8,m;                        // n:叶节点的数目

	HuffmanCode HC;HuffmanTree HT;
	HuffmanCoding(HT,HC,s,n);
	PrintHuffmanCode(HC,s,n);

	printf("\n解码测试\n");
	char*c="0011101110101#";
	if(Rewin(HT,c,x,n,m))//x为每个字符在s中的下标+1;
	printf("编码 %s,解码后对应的权重值如下:\n", c);
	for(int i=0;i<m;i++)
        printf("%4d ",s[x[i]-1]);
}

运行如下:

权重: 7,编码:1100
权重: 19,编码:00
权重: 2,编码:11101
权重: 6,编码:1111
权重: 32,编码:10
权重: 3,编码:11100
权重: 21,编码:01
权重: 10,编码:1101

解码测试
编码 0011101110101#,解码后对应的权重值如下:
19 2 10 21

这样关于建造霍夫曼树,求霍夫曼编码,以及根据霍夫曼编码还原。下一部分在讲文件压缩的实现。

综合实验: 1. 问题描述 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。这要求在发送端通过个编码系统对待传输数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要个完整的编/译码系统。试为这样的信息收发站编写哈夫曼码的编/译码系统。 2. 基本要求 个完整的系统应具有以下功能: (1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼,并将它存于文件hfmTree中。 (2) E:编码(Encoding)。利用已建好的哈夫曼(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。 (3) D:译码(Decoding)。利用已建好的哈夫曼文件CodeFile中的代码进行译码,结果存入文件Textfile中。 (4) P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。 (5) T:印哈夫曼(Tree printing)。将已在内存中的哈夫曼以直观的方式(比如)显示在终端上,同时将此字符形式的哈夫曼写入文件TreePrint 中。 3. 测试数据 用下表给出的字符集和频度的实际统计数据建立哈夫曼,并实现以下报文的编码和译码:“THIS PROGRAME IS MY FAVORITE”。 字符 A B C D E F G H I J K L M 频度 186 64 13 22 32 103 21 15 47 57 1 5 32 20 字符 N O P Q R S T U V W X Y Z 频度 57 63 15 1 48 51 80 23 8 18 1 16 1
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值