哈夫曼树+哈夫曼编码

一、前导概念

1.节点 i 的路径长度:从根节点到节点 i 的路径上所经过的边数。

结点 k 的路径⻓度就是从根结点 A 到结点 k 的路径上的连接数,也就是我们看到红⾊连边,也就是3

2.树的路径长度:(版本一)所有节点的路径长度之和为该树的路径长度 

                            (版本二)所有叶子节点的路径长度

内部路径长度:所有内部节点(除叶子节点外的节点)的路径长度

外部路径长度(在一些资料中被称为树的路径长度):所有叶子节点的路径长度 

上图树的路径长度为:1*3+2*6+ 3*1=18,内部路径长度为5,外部路径长度为13 

3.节点 i 的带权路径长度:节点 i 的路径长度 * 节点权值

⽐如规定结点 K 的权重 / 权值为 4 ,结点 K 的路径⻓度为 3 ,那么带权路径⻓度就是 3 * 4 = 12

4.树的带权路径长度(WPL):所有叶子节点的带权路径长度之和

给出n个节点(都带有权值),可以再加入若干节点(不确定的),现在去建立一棵树,用这n个节点全部做叶子节点,自行加入的节点做内部节点去建立一棵二叉树,其中WPL最小的一棵二叉树称为哈夫曼树(最优二叉树)

哈夫曼树的WPL值唯一

哈夫曼树样子不唯一

二、构建哈夫曼树

对于给定的n个节点(带权值且只做叶子节点),构建哈夫曼树 

树的合并一开始认为这n节点是n棵树:

        每次找根节点权值最小的两棵树(x,y),再新加入一个节点做x和y节点的父亲,新节点的权值为(x+y),此时将x y两棵树合并出了一棵,这棵树的根节点就是z(把该过程重复n-1次)

即可得到哈夫曼树

三、哈夫曼编码

应用场景:通过编码进行数据压缩:哈夫曼编码

1.引子

定⻓编码
ASCII 编码、 Unicode 编码。 ASCII 编码每⼀个字符使⽤ 8 bit ,能够编码 256 个字符; Unicode
编码每个字符占 16 bit ,能够编码 65536 个字符,包含所有 ASCII 编码的字符。
假设我们要使⽤定⻓编码对由符号 A B C D E 构造的消息进⾏编码,对每个字符编码需要多
少位呢?  ⾄少需要3 位, 2 bit 位不够表示五个字符,只能表示 4 个字符。
如果对 DEAACAAAAABA 进⾏编码呢? 总共 12 个字符,每个字符需要 3bit ,总共需要 36 位。
定⻓编码的缺陷
浪费空间!对于仅包含 ASCII 字符的纯⽂本消息, Unicode 使⽤的空间是 ASCII 的两倍,两种⽅式
都会造成空间浪费;字符 “ a” “ e” 的出现频率⽐ “ q” “ z” 的出现频率⾼,但是他们都占⽤了相同位数的空间。要解决定⻓编码的缺陷,便有了下⾯的变⻓编码。

假设有一种编码方式中每个字符使用2个bit

可以画出下面的编码树

 

00 A

01 B

10 C

11 D

把每个字符出现的频率看成节点的权值,则该树的WPL值为消息的位数

例如,上图中ABCAAD 可以画出下图

可以看出该消息的位数为(3+1+1+1)*2=12

让字符所在节点做叶子节点,如何使这条消息的编码最小,即如何使这条消息的WPL最小

----> 使用哈夫曼树来构造变长编码

变⻓编码
单个编码的⻓度不⼀样,可以根据整体出现的频率来调节,出现的频率越⾼,编码⻓度越短。
变⻓编码优于定⻓编码的是,变⻓编码可以将短编码赋予平均出现频率较⾼的字符,同⼀消息的
编码⻓度⼩于定⻓编码。
这个时候⼜有⼀个问题,字符有⻓有短,我们怎么知道⼀个字符从哪⾥开始,⼜从哪⾥结束呢?
如果位数固定,就没这个问题了。
哈夫曼编码属于变长编码

2.如何使用哈夫曼树来构造变长编码

(1)先统计一条消息中每个字符的出现次数(频率)---->节点权值

(2)构造哈夫曼树

(3)给父亲节点的两条边标1,0

3.前缀属性原则

短的编码不能是长编码的前缀

例如:

A:001

B: 0

C: 01

此时无法判断 001表示的是A还是BC

哈夫曼编码一定满足前缀属性原则:长编码的前缀不可能是叶子节点

4.建立哈夫曼树

(1)把每个字符的频率看成节点的权值,建立哈夫曼树

        1.1)查找最小的两个根节点

        1.2)合并;加入一个新的根节点

用什么数据结构存储树?----->结构体数组模拟树

 结点中要存储的信息:权值,父亲的下标,左右孩子节点的下标

        如何判断节点是根节点--->父亲节点的下标==本身的下标

5.代码(构建哈夫曼树+哈夫曼编码)

#include <stdlib.h>
#include <stdio.h>
# include <string.h>
//数组模拟树
typedef struct {
	int w;//Ȩ权值
	int f;//父亲节点的下标
	int l,r;//左右指针的下标
	
}HuffmanNode,*HuffmanTree; 
void find(HuffmanTree t,int x,int* w1,int* w2)
{
	//先找最小
	int minn=0;//最小值的下标
	for(int i=1;i<=x;i++)
	{
		if(t[i].f==i)
		{
			minn=i;
			break;
		}
	} 
	for(int i=1;i<=x;i++)
	{
		if(t[i].f ==i)
		{
			if(t[i].w<t[minn].w)
			{
				minn=i;
			}
		}
	}
	*w1=minn;//最小的根节点下标保存至w1中
	//找第二小
	 for(int i=1;i<=x;i++)
	{
		if(t[i].f==i&&i!=(*w1))
		{
			minn=i;
			break;
		}
	} 
	for(int i=1;i<=x;i++)
	{
		if(t[i].f==i&&i!=(*w1))
		{
			if(t[i].w<t[minn].w)
			{
				minn=i;
			}
		}
	}
	*w2=minn;                                                                                                    
}
HuffmanTree createHuffmanTree(int *wi,int n)
{
	int m=2*n;//从1开始,(2n-1)个数组
	HuffmanTree t=(HuffmanTree)malloc(sizeof(HuffmanNode)*m);
	for(int i=1;i<m;i++)
	{
		t[i].f=t[i].l =t[i].r=0;
		t[i].w=0;
	}
	for(int i=1;i<=n;i++)
	{
		t[i].w=wi[i-1];//t数组从1开始,w数组从0开始
		t[i].f=i;
	}
	int w1,w2;//权值最小的两个根节点的下标
	for(int i=n+1;i<m;i++)
	{
		find(t,i-1,&w1,&w2);//地址传参
		t[w1].f=t[w2].f=i;
		t[i].f=i;
		t[i].w=t[w1].w+t[w2].w;
		t[i].l=w1;
		t[i].r=w2;
	}
	return t;
}
char**  ctreateHuffmanCode(HuffmanTree t,int n)
{
	char *temp=(char*)malloc(sizeof(char)*n);//暂存数组,下标:0~~n-1
	char **code=(char**)malloc(sizeof(char*)*n);//最终保存数组
	int start;//一开始放编码的位置
	int pos,p;
	for(int i=1;i<=n;i++)
	{
		start=n-1;
		temp[start]='\0';
		pos=i;
		p=t[pos].f;//p是pos的父亲
		while(t[pos].f!=pos)
		{
			start--;
			if(t[p].l==pos)
			{
				temp[start]='0';
			}else{
				temp[start]='1';
			}
			pos=p;
			p=t[pos].f;	
		}
		code[i-1]=(char*)malloc(sizeof(char)*(n-start));
		strcpy(code[i-1],&temp[start]);
		
	}
	free(temp);
	temp=NULL;
	return code;
} 
int main()
{	
	char s[8] = {'A', 'B', 'C', 'D','E', 'F', 'G', 'H'};
	int w[8] = {5, 29, 7, 8,14, 23, 3, 11};
	HuffmanTree tree = createHuffmanTree(w,8);
    char **code= ctreateHuffmanCode(tree,8); 
    for(int i=0;i<8;i++)
    {
    	printf("%c : %s\n",s[i],code[i]);
	}
	return 0;	 
}

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang中的哈夫曼树编码是一种数据压缩算法,适用于将大数据量进行高效的压缩和解压缩操作。哈夫曼树编码的原理是根据字符出现的频率构建一棵树,出现频率较高的字符使用较短的编码,出现频率较低的字符使用较长的编码,从而实现数据的压缩。 在Golang中,可以使用哈夫曼树编码库来实现这一算法。首先,需要统计字符的出现频率,可以通过遍历待压缩的数据来进行统计。然后,根据字符的频率构建哈夫曼树,可以使用优先队列或堆来实现。 一旦构建好了哈夫曼树,就可以生成每个字符对应的哈夫曼编码编码过程中,从根节点开始,遍历树的路径,当遇到左子树时将路径上加入0,遇到右子树时加入1,直到到达叶子节点,将路径上的编码记录下来。最终,每个字符对应的哈夫曼编码就是根据路径上的0和1组成的。 对于压缩数据,在编码时将每个字符替换成对应的哈夫曼编码,将压缩后的编码写入到新的文件中。对于解压缩数据,需要读取压缩文件中的编码,根据哈夫曼树的结构进行逐个字符的解码操作。 总而言之,Golang中的哈夫曼树编码是一种高效的数据压缩算法,通过统计字符出现的频率构建哈夫曼树,然后生成每个字符对应的哈夫曼编码,从而实现数据的压缩和解压缩操作。通过使用相关的库,可以方便地实现这一算法

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值