【数据结构】哈夫曼树及其应用


哈夫曼树的基本概念

哈夫曼树又称“最优二叉树”或“最优树”

最优树:带权路径长度(WPL)最短的树
最优二叉树:带权路径长度(WPL)最短的二叉树
"带权路径长度最短”是在"度相同”的树中比较而得的结
果,因此有最优二叉树、最优三叉树之称等等。

  • 路径:从树中一个结点到另一个结点之间的分支构成这 两个结点间的路径。
  • 结点的路径长度:两结点间路径上的分支数。

在这里插入图片描述

  • 树的路径长度:从树根到每一个结点的路径长度之和。 记作:TL

结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树。

在这里插入图片描述

  • 权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
  • 结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。
  • 树的带权路径长度(Weighted Path Length,WPL ):树中所有叶子结点的带权路径长度之和。

在这里插入图片描述
在这里插入图片描述

满二叉树不一定是哈夫曼树;
哈夫曼树中权越大的叶子离根越近;
具有相同带权结点的哈夫曼树不惟一。

哈夫曼树的构造算法

【核心思想】
哈夫曼树中权越大的叶子离根越近。
贪心算法:构造哈夫曼树时首先选择权值小的叶子结点。

  • 哈夫曼算法(构造哈夫曼树的方法)

① 构造森林全是根:
根据n个给定的权值{W1, W2, … Wn}构成n棵二叉树的森林F={T1, T2… Tn},其中Ti只有一个带权为Wi的根结点。
② 选用两小造新树:
在F中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
③ 删除两小添新人:
在F中删除这两棵树,同时将新得到的二叉树加入森林中。
④ 重复②和③,直到森林中只有一棵树为止,这棵树即为哈夫曼树。
在这里插入图片描述

  • 构造哈夫曼树的特点:

1、哈夫曼树的结点的度数为0或2,没有度为1的结点。(结点的度:结点拥有子结点的数量)
2、包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,两两结点合并就产生一个新结点,共产生n-1个新结点。
3、包含n个叶子结点的哈夫曼树中,n个叶子结点和n-1个新结点,共有2n- 1个结点。

哈夫曼树构造算法的实现

采用顺序存储结构——一维结构数组

结点类型定义:

typedef struct{
	int weight;
	int parent,lch,rch;
}HTNode,*HuffmanTree;

哈夫曼树中共有2n-1个结点,不使用0下标,数组大小为2n。
在这里插入图片描述
在这里插入图片描述
1、初始化HT[1…2n-1]:Ich=rch=parent=0;
2、输入初始n个叶子结点:置HT[1…n]的weight值;
3、进行以下n-1次合并,依次产生n-1个结点HT[i],i=n+1…2n-1:
① 在HT[1…i-1]中选两个未被选过(从parent==0的结点中选)的weight最小的两个结点HT[s1]和HT[s2],s1、s2为两个最小结点下标;
② 修改HT[s1]和HT[s2]的parent值:HT[s1].parent=i;HT[s2].parent=i;
③ 修改新产生的HT[i]:
HT[i].weight=HT[s1].weight+HT[s2].weight;
HT[i].Ich=s1;HT[i].rch=s2;

void CreateHuffmanTree(HuffmanTree HT,int n){
	if(n<=1)
		return;
	m=2*n-1;//数组共2n-1个元素
	HT=new HTNode[m+1];//0号单元未用,HT[m]表示根结点
	for(i=1;i<=m;++i)//将2n-1个元素的lch、rch、parent置为0
		HT[i].lch=0;
		HT[i].rch=0;
		HT[i].parent=0;
	for(i=1;i<=n;++i)
		cin>>HT[i].weight;//输入前n个元素的weight值
	//初始化结束,下面开始建立哈夫曼树
	for(i=n+1;i<=m;i++){//合并产生n-1个结点——构造Huffman树
		Select(HT,i-1,s1,s2);//在HT[k](1≤K≤i-1)中选择两个双亲域为0,且权值最小的结点,并返回它们在HT中的序号s1和s2
		HT[s1].parent=i;//最小的结点是当前这个结点,表示从F中删除s1,s2
		HT[s2].parent=i;
		HT[i].lch=s1;//s1、s2分别作为i的左右孩子
		HT[i].rch=s2;
		HT[i].weight=HT[s1].weight+HT[s2].weight;//i的权值为左右孩子权值之和
}

哈夫曼编码

哈夫曼编码思想

  • 关键:

要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀。——这种编码称做前缀编码。(避免出现重码的问题)

  • 方法:

1、统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短)。
2、利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。则概率越大的结点,路径越短。
3、在哈夫曼树的每个分支上标上0或1:结点的左分支标0,右分支标1;把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码。
在这里插入图片描述

  • 性质:

1、哈夫曼编码是前缀码
因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码就不可能是其它叶结点编码的前缀。
2、哈夫曼编码是最优前缀码
因为哈夫曼树的带权路径长度最短,故字符编码的总长最短。

哈夫曼编码算法实现

n个叶子结点的哈夫曼树最多只有n-1层。

在这里插入图片描述

void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n){//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
	HC=new char *[n+1];//分配n个字符编码的头指针矢量
	cd=new char[n];//分配临时存放编码的动态数组空间
	cd[n-1]='\0';//编码结束符
	for(i=1;i<=n;++i){//逐个字符求哈夫曼编码
		start=n-1;
		c=i;
		f=HT[i].parent;
		while(f!=0){//从叶子结点开始向上回溯,直到根结点
			--start;//回溯一次start向前指一个位置
			if(HT[f].lchild==c)//结点c是f的左孩子,则生成代码0
				cd[start]='0';
			else//结点c是f的右孩子,则生成代码1
				cd[start]='1';
			c=f;f=HT[f].parent;
		}
		HC[i]=new char[n-start];//为第i个字符串编码分配空间
		strcpy(HC[i],&cd[start];//将求得的编码从临时空间cd复制到HC的当前行中,strcpys是string copy(字符串复制)的缩写
	}
	delete cd;//释放临时空间
}//CreateHuffmanCode

文件的编码和译码

1、编码:
① 输入各字符及其权值
② 构造哈夫曼树HT[i]
③进行哈夫曼编码HC[i]
④查HC[i],得到各字符的哈夫曼编码

2、解码:
① 构造哈夫曼树
② 依次读入二进制码
③ 读入0,则走向左孩子;读入1,则走向右孩子
④ 一旦到达某叶子时,即可译出字符
⑤ 然后再从根出发继续译码,指导结束


  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值