数据结构----哈夫曼编码

一、基本概念

在一棵二叉树中,我们定义从A结点到B结点所经过的分支序列为从A结点到B结点的路径;定义从A结点到B结点所经过的分支个数为从A结点到B结点的路径长度;定义从二叉树的根结点到二叉树中所有叶结点的路径长度之和为该二叉树的路径长度。如果二叉树中的叶结点都带有权值,则可以把这个定义加以推广。设二叉树有n个带权值的叶结点,定义从二叉树的根结点到二叉树中所有叶结点的路径长度与相应叶结点权值的乘积之和为该二叉树的带权路径长度(WPL),即:
在这里插入图片描述
式中,wi为第i个叶结点的权值,li为从根结点到第i个叶结点的路径长度。对图a所示的二叉树,其带权路径长度为:
在这里插入图片描述
(a)WPL=1×2+3×2+5×2+7×2=32
(b)WPL=1×2+3×3+5×3+7×1=33
(c)WPL=7×3+5×3+3×2+1×1=43
(d)WPL=1×3+3×3+5×2+7×1=29
我们把其中具有最小带权路径长度的二叉树称作哈夫曼(Huffman)树(或称最优二叉树)。可以证明,图(d)的二叉树是一棵哈夫曼树。

二、哈夫曼树的画法

根据哈夫曼树的定义,一棵二叉树要使其带权路径长度WPL值最小,必须使权值越大的叶结点越靠近根结点。哈夫曼树构造算法如下。① 由给定的n个权值{w1, w2, …, wn}构造n棵只有根结点的二叉树,从而得到一个二叉树森林F={T1,T2, …, Tn}。② 在二叉树森林 F 中选取根结点的权值最小和次小的两棵二叉树作为新的二叉树的左右子树构造新的二叉树,新的二叉树的根结点权值为左右子树根结点权值之和。③ 从二叉树森林 F 中删除作为新二叉树左右子树的两棵二叉树,将新二叉树加入到二叉树森林F中。④ 重复步骤②和③,当二叉树森林F中只剩下一棵二叉树时,这棵二叉树就是所构造的哈夫曼树。对于一组给定的叶结点,设它们的权值集合为{7,5,3,1},按哈夫曼树构造算法对此集合构造哈夫曼树的过程如图所示。
在这里插入图片描述
哈夫曼树可用于解决最优化问题。例如,由哈夫曼树构造的哈夫曼编码可用于构造代码总长度最短的电文编码方案。

三、哈夫曼编码的设计与实现

1.设计

对于哈夫曼编码问题,在构造哈夫曼树时,要求能方便地实现从双亲结点到左右孩子结点的操作;在进行哈夫曼编码时,又要求能方便的实现从孩子结点到双亲结点的操作。因此,我们设计哈夫曼树的结点存储结构为双亲孩子存储结构。二叉树结点的双亲孩子存储结构既可以用常规指针实现,也可以用仿真指针实现,这里采用仿真指针实现。另外,每个结点还要有权值域。为了判断一个结点是否已加入到哈夫曼树中,每个结点还要有一个标志域flag:当flag=0时,表示该结点尚未加入到哈夫曼树中;当flag=1时,表示该结点已加入到哈夫曼树中。这样,每个结点的数据结构设计为:
在这里插入图片描述

从哈夫曼树求叶结点的哈夫曼编码实际上是从叶结点到根结点路径分支的逐个遍历,每经过一个分支,就得到一位哈夫曼编码值。因此需要用一个数组bit[MaxBit]保存每个叶结点到根结点路径所对应的哈夫曼编码。由于是不等长编码,还需要一个数据域start表示每个哈夫曼编码在数组中的起始位置。这样,每个叶结点的哈夫曼编码都是从数组bit的起始位置start开始到数组结束位置中存放的0和1序列。存放哈夫曼编码的数据元素结构为:
在这里插入图片描述

2.双亲孩子仿真指针存储结构

头文件

typedef struct{
	int weight;//权值 
	int flag;//标记 
	int parent;//双亲结点下标 
	int leftChild;//左孩子下标 
	int rightChild;//右孩子下标 
}HaffNode;//哈夫曼树的结点结构 

typedef struct{
	int bit[MaxN];//数组 
	int start;//编码的起始小标 
	int weight;//字符的权值 
}Code;//哈夫曼编码的结构 


/*建立叶节点个数为n,权值数组为weight的哈夫曼树haffTree*/
void Haffman(int weight[], int n, HaffNode haffTree[]){
	int i,j,m1,m2,x1,x2;
	
	for(i=0;i<2*n-1;i++){//哈夫曼树的初始化,有n个叶子结点的二叉树共有2n-1个结点 
		if(i<n){
			haffTree[i].weight = weight[i];
		}else{
			haffTree[i].weight = 0;
		}
		
		haffTree[i].parent = -1;
		haffTree[i].flag = 0;
		haffTree[i].leftChild = -1;
		haffTree[i].rightChild = -1;
	}
	
	
	//构造哈夫曼树的n-1个非叶子结点 
	for(i=0;i<n-1;i++){
		m1 = m2 =MaxValue;
		x1 = x2 = 0;
		for(j=0;j<n+i;j++){
			if(haffTree[j].weight<m1&&haffTree[j].flag==0){
				m2 = m1;
				x2 = x1;
				m1 = haffTree[j].weight;
				x1 = j;
			}else if(haffTree[j].weight<m2&&haffTree[j].flag==0){
				 m2 = haffTree[j].weight;
				 x2 = j;
			}
		}
		
		
		haffTree[x1].parent = n+i;
		haffTree[x2].parent = n+i;
		haffTree[x1].flag = 1;
		haffTree[x2].flag = 1;
		haffTree[n+i].weight = haffTree[x1].weight + haffTree[x2].weight;
		haffTree[n+i].leftChild = x1;
		haffTree[n+i].rightChild = x2;
	} 
}


/*由n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode*/
void HaffmanCode(HaffNode haffTree[], int n, Code haffCode[]){
	Code *cd = (Code *)malloc(sizeof(Code));
	int i,j,child,parent;
	
	for(i=0;i<n;i++){	//求n个叶结点的哈夫曼编码 
		cd->start = n-1;//不等长的编码的最后一位为n-1 
		cd->weight = haffTree[i].weight;//取得编码对应的权值 
		child = i;
		parent = haffTree[child].parent;
		
		while(parent!=-1){	//由叶结点向上直到根结点 
			if(haffTree[parent].leftChild==child){
				cd->bit[cd->start]=0;//左孩子分支编码为0
			}else{
				cd->bit[cd->start]=1;//右孩子分支编码为1 
			}
			
			cd->start--;
			child = parent;
			parent = haffTree[child].parent;
		}
		
		for(j=cd->start+1;j<n;j++){
			haffCode[i].bit[j] = cd->bit[j];//保存每个叶结点的编码 
			haffCode[i].start = cd->start+1;//保存叶结点编码的起始位 
			haffCode[i].weight = cd->weight;//保存编码对应的权值 
		}
	}
} 

测试

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

#define MaxValue 10000	//初始设定的权值最大值 
#define MaxBit 4	//初始设置的最大编码位数 
#define MaxN 10		//初始设置的最大节点个数 
#include "Haffman.h"

void main(void){
	int i,j,n = 4;
	int weight[] = {1,3,5,7};
	HaffNode *myHaffTree = (HaffNode *)malloc(sizeof(HaffNode)*(2*n+1));
	Code *myHaffCode = (Code *)malloc(sizeof(Code)*n);
	
	if(n>MaxN){
		printf("给出的n越界,请修改MaxN!\n");
		exit(1);
	}
	
	Haffman(weight,n,myHaffTree);
	HaffmanCode(myHaffTree,n,myHaffCode);
	
	for(i=0;i<n;i++){//输出每个叶结点的哈夫曼编码 
		printf("Weight = %d  Code = ",myHaffCode[i].weight);
		for(j=myHaffCode[i].start;j<n;j++){
			printf("%d",myHaffCode[i].bit[j]);
		}
		printf("\n");
	}
	getch();
}
  • 0
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值