哈夫曼树及其应用

哈夫曼树及其应用

1.由下面例子引出哈夫曼树

哈夫曼树:带权路径长度WPL最小的二叉树

学生成绩段分布占比

比较方案一

if(a < 60)
	b='不及格';
else if (a < 70)
	b='及格';
else if (a < 80)
	b='中等';
else if (a < 90)
	b='良好';
else
	b='优秀';


带权路径长度 = (叶子结点对应的权值 w k w_k wk×根结点到树中叶子结点的路径长度 l k l_k lk)之和
W P L = ∑ k = 1 n w k l k WPL = \sum_{k=1}^n w_kl_k WPL=k=1nwklk
带权路径长度 W P L = 5 × 1 + 15 × 2 + 40 × 3 + 30 × 4 + 10 × 4 = 315 WPL=5\times 1+15\times 2+40\times 3+30\times 4+10\times 4=315 WPL=5×1+15×2+40×3+30×4+10×4=315

比较方案二

if(a < 80){
	if(a < 70)
		if(a < 60)
			b='不及格';
		else
			b='及格';
	else
		b='中等';
}
else{
	if(a < 90)
	 	b='良好';
	else
		b='优秀';
}


带权路径长度 W P L = 5 × 3 + 15 × 3 + 40 × 2 + 30 × 2 + 10 × 2 = 220 WPL=5\times 3+15\times 3+40\times 2+30\times 2+10\times 2=220 WPL=5×3+15×3+40×2+30×2+10×2=220

方案一的WPL=315
方案二的WPL=220
相比之下方案二的效率高一些,但这并不是最优方案,即效率不是最高(WPL不是最小)

2.哈夫曼树的存储表示

一棵有 n n n 个叶子结点的哈夫曼树共有 2 n − 1 2n-1 2n1 个结点,可以存储在一个大小为 2 n − 1 2n-1 2n1 的一维数组中

//哈夫曼树存储在一维数组中
typedef struct{	
	int weight;	//结点的权值
	int parent,lchild,rchild;	//结点的双亲,左孩子,右孩子的下标
}HTNode,*HuffmanTree;

为方便,数组0号不用,从1号开始用,数组大小为 2 n 2n 2n ,将叶子结点集中存储在前面部分 1~n 个位置,后面的位置存储其余非叶子结点

3.构造哈夫曼树


(1)按结点对应的权值大小,从小到大顺序排列成有序序列
A 5 、 E 10 、 B 15 、 D 30 、 C 40 A5、E10、B15、D30、C40 A5E10B15D30C40

(2)取当前序列中权值最小的两个结点作为新结点 N 1 N_1 N1,并且权值小的作为左孩子

(3)现在有序序列变为: N 1 15 、 B 15 、 D 30 、 C 40 N_115、B15、D30、C40 N115B15D30C40
(4)重复步骤(2)现在有序序列变为: N 2 30 、 D 30 、 C 40 N_230、D30、C40 N230D30C40

(5)现在有序序列变为: C 40 、 N 3 60 C40、N_360 C40N360,最后这两个结点构成一个新结点(权值小的作为新结点的左孩子)

(6)这样就完成了哈夫曼树的构造


带权路径长度 W P L = 40 × 1 + 30 × 2 + 15 × 3 + 10 × 4 + 5 × 4 = 205 WPL=40\times 1+30\times 2+15\times 3+10\times 4+5\times 4=205 WPL=40×1+30×2+15×3+10×4+5×4=205
方案一的WPL=315
方案二的WPL=220
此方案的WPL=205,所以此方案的效率最高

构造哈夫曼算法实现

void CreateHuffmanTree(HuffmanTree &HT, int n){ 	//n个叶子结点的哈夫曼树
	if(n <= 1)
		return;
//初始化开始
	m=2*n-1;	//n个叶子结点的哈夫曼树共有 2n-1 个结点
	HT = new HTNode[m+1];	//下标0号不用,动态分配m+1个单元,HT[m]表示根结点
	for(i=1; i<=m; ++i){	//1~m单元存储着叶子结点,将m=2n-1个结点初始化
		HT[i].parent=0;
		HT[i].lchild=0;
		HT[i].rchild=0;
	}
	for(i=1; i<=n; ++i)	//前n个单元存储着叶子结点,输入叶子结点的权值
		cin >> HT[i].weight;
//初始化结束

//创建哈夫曼树
	for(i=n+1; i<=m; ++i){	//n~m单元内存储着非叶子结点
		Select(HT,i-1,s1,s2);	//从当前森林中选择双亲为0且权值最小的两个树根结点s1、s2
		//在HT[k](1 <= k <= i-1)中选择两个其双亲域为0且权值最小的结点,并返回他们在HT中的序号s1和s2
		HT[s1].parent=i;
		HT[s2].parent=i;
		//得到新结点i,从森林中删除s1和s2,将s1和s2的双亲域由0改为i
		HT[i].lchild = s1;	//s1作为结点i的左孩子
		HT[i].rchild = s2;	//s2作为结点i的有孩子
		//结点i的权值为左右孩子权值之和
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
}

4.哈夫曼编码

假设传输内容“BADCADFEED”,按照下表编码

则原编码二进制串:001000011010000011101100100011 每个字母都占3位,事实上在数据传输时每个字母出现频率不同。

假设六个字母频率 A27、B8、C15、D15、E30、F5
左图为构造哈夫曼树过程的权值显示
右图为将权值左分支改为0,右分支改为1

由右图得编码

原编码二进制串:001000011010000011101100100011(共30个字符)
新编码二进制串:1001010010101001000111100 (共25个字符)
数据被压缩了,在解码时,还要用到哈夫曼树,即发送方和接收方必须要约定好同样的哈夫曼编码规则

5.哈夫曼编码算法

由于每个哈夫曼编码是变长编码,因此使用一个指针数组来存放每个字符编码的首地址

哈夫曼编码表的存储表示

typedef char **HuffmanCode;	//动态分配数组存储哈夫曼编码表

根据哈夫曼树求哈夫曼编码
以叶子结点为出发点,向上回溯至根结点,回溯时走左分支则生成0,走右分支则生成1


根据已知的哈夫曼树求哈夫曼编码

void CreateHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n){ //n个叶子结点的哈夫曼树
	//HC的下标0不使用,从1开始,数组大小n+1
	HC = new char*[n+1];	//存储动态数组cd首地址
	//cd的下标0使用
	cd = new char[n];	//存储叶子结点对应的哈夫曼编码
	cd[n-1] = '\0';		//数组cd末尾填入结束符
	for(i=1; i<=n; ++i){	//从叶子向上回溯至根结点,求n个叶子结点对应的哈夫曼编码
		start = n-1;	//start开始指向cd数组的最后
		c = i;	
		f = HT[i].parent;	//f指向结点c的双亲结点
		while(f != 0){
			--start;	//回溯一次start向前指一个位置
			if(HT[f].lchild == c)	//双亲HT[f]的左孩子为下标c处的结点
				cd[start]='0';	//左支生成0
			else
				cd[start]='1';	//右支生成1
			//继续向上回溯
			c = f;	
			f = HT[f].parent;
		}
		HC[i] = new char[n-start];	//为某个叶子结点的哈夫曼编码数组cd分配空间
		strcpy(HC[i], &cd[start]);	//将求得的哈夫曼编码从临时空间cd复制(数组首地址)到HC的当前行中
	}
	delete cd;	//释放临时空间
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Uncertainty!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值