哈夫曼编码(Huffman)链式

简介:

哈夫曼编码是贪心算法的应用,并可以得到最优解。

用于:压缩

传输举例:

传:A、B、C、D

ASCII:01100001、01100010、01100011、01100100

机器传输的时候都是传送的ASCII码,每8位为一个数。所以如果传输量大的话,传输速率很慢。

压缩可以将无意义的0去掉,就比如A,可以把第一个0去掉。但是这样会出现不知道怎么划分一个字符的问题,不知道到底按几位一分,因为每个都不一样。

所以哈夫曼认为不能用ASCII码进行传输,他给这些字符重新进行了编码。

哈夫曼编码的依据:(最优二叉树)

最优二叉树,即(哈夫曼树)。

基本概念:

1. 结点的权:赋予叶子结点一些有意义的值。

2. 节点的路径长度:从根结点出发,到当前结点的变得个数。(最多:层数-1)

3. 节点的带权路径长度:W * L (结点的权 * 节点的路径长度)

4. 二叉树的带权路径长度:一颗二叉树的所有叶子结点的带权路径长度之和。

5. 最优二叉树:一颗二叉树的带权路径长度之和最小,称为最优二叉树。

例题:

问题描述:

有一篇英文文章,怎样传输,使得传输量达到最小?

问题分析:

如果按ASCII编码,可以准确的传送,但是每个单词都是固定的8bit,不能减少传送量。

应该让出现频率高的单词,编码的长度短,即带权路径小,即路径长度短。

出现频率低的单词,编码的长度长,即带权路径大,即路径长度长。

问题:如果A的编码是111,B的编码是11,系统怎么识别,该从第几位断开识别?

解决:所以必须保证短码不能是长码的前缀。每一个需要编码的都是叶子结点。

图例:

例:英文文章中的字符及其出现次数为:

A(9次) B(3次) C(6次) D(4次)

下面有三种编码方式:

                                 

wpl = (9+3+6+4)*2 = 44                        wpl = 3 + 4*2 + (9+6)*3 = 56                      wpl = 9 + 6 * 2 + (3+4)*3 = 42 

可以明显的看出第三种方式的二叉树的带权路径长度最短,所以它是最优二叉树。

编码:

A:0    B:110   C: 10  D:111

特点:

每一个结点都是叶子结点,所以不可能出现一个码是一个码的前缀码,要想成为前缀码,那么这个结点必定是某一个码的双亲。

一:Huffman tree的构建(链式)

1. 森林       F = { A、B、C、D}

2. 应该从下往上构建,通过每次找两个值最小的二叉树,然后构建成一个新的二叉树,再放回森林中,依次类推,直到森林中只剩下一颗二叉树。

过程图:

                     

哈夫曼树的特点:(最优二叉树)

1. 最优二叉树没有单分支结点,只有双分支结点和叶子结点两种。叶子结点总是比双分支结点多一个。所以如果有N个叶子结点,那么总结点个数一定是2*N-1个。

2. 一颗哈夫曼树的形态是任意的,但是全职较大的节点一定要往上放,权重较小的结点一定要往下放。所以创建哈夫曼树一定要从低向上建,所以需要一个森林,里面存放各个小二叉树。

3. 结点设计:两个指针域,一个数据域,数据域分为两块:字符、权值。

 // 约定:没有字符的双亲结点,word域用‘X'代替。 

typedef struct node {
	char word;	// 存储此结点的字符,如果没有用'x'代替
	int weight;	// 存储此字符出现的频率
	struct node * left;	// 左子女
	struct node * right;// 右子女 
}HuffNode;

4. 森林:

设有n个叶子:(动态数组:因为也不知道 n是几)

A>定义森林:

为什么用数组不用链表?

因为链表是在不知道将来可能存储多少个,避免长度不够或者空间浪费的情况下使用的。但是相对来说,数组的使用更为简单方便。能用数组就用数组。在这里我们知道最多存储N个在森林中,每次都会减少一个,所以选用数组。

B>初始化森林:

// 创建森林(动态数组) 
F = (HuffNode **)malloc(n * sizeof(HuffNode *));
// 循环往森林中放值
int i;
for (i = 0; i < n; i++){
	F[i] = (HuffNode *)malloc(sizeof(HuffNode));
	F[i]->left = F[i]->right = NULL;
	fflush(stdin);
	printf("请输入第%d个字符:",i+1);
	char word;
	scanf("%c",&word);
	F[i]->word = word;
	fflush(stdin);
	printf("请输入第%d个字符出现的频率:",i+1);
	int weight;
	scanf("%d",&weight);
	F[i]->weight = weight;
} 

5. 实现最优二叉树

问题:

A>怎么找最小,次小值?

min1存储最小值的下标,min2存储次小值的下标。

F[min1]指向最小的二叉树,F[min2]指向次小的二叉树。

不论a[0]小,还是a[1]小,直接将min1 = 下标0,min2 = 1;

然后从下标1开始循环找最小值、次小值。

代码如下:

min1 = 0; min2 = 1;
for(i = 1; i < n; i++){
	if(a[i] < a[min1]){	// 比最小值还要小 
		min2 = min1;	// 次小值存之前的最小值 
		min1 = i;		// 更新最小值 
	}else if(a[i] < a[min2]) {	// 只比次小值小 
		min2 = i; 
	}
}

 

B>怎么合成一个新的二叉数?放在哪里?

给找到的F[min1]和F[min2]生成双亲,然后放入min1的位置,min2的位置置为NULL;

代码如下:

//合成一个新的二叉树,放入min1的位置
HuffNode * p = (HuffNode *)malloc(sizeof(HuffNode));
p->word = 'x';
p->weight = F[min1]->weight + F[min2]->weight;
p->left = F[min1];
p->right = F[min2];
//放回森林中
F[min1] = p;
F[min2] = NULL;

C>怎么判断森林只有一个二叉树了?

循环n-1次,森林中绝对只剩下了一棵二叉树。每次都是取出来两个,放回去一个,就意味着每次只减少一个,所以n-1次,就剩下来一个。另一个角度:每次都是在生成双亲的过程,哈夫曼树是一个最优二叉树:双亲结点 = 叶子结点 - 1.

D>森林跑着就会出现NULL,那min1,min2该怎么放?

所以每次放置的时候一定要min1放置在第一个非NULL处,min2放置在第二个非NULL结点处。

# include <stdio.h>
# include <stdlib.h>
/*
	链式哈夫曼树 
*/
 
// 定义结点类型(HuffNode)
typedef struct node{
	char word;				// 字符 
	int weight;				// 权值 
	struct node * left;		// 左子女 
	struct node * right;	// 右子女 
}HuffNode; 
 
// 申明函数
HuffNode * CreatHuffmanTree(HuffNode ** F, int n); 
void OrderBy(HuffNode * root);
 
 
// 主函数 
int main(void)
{
	
	int i, w, ch;
	int n;			// 存储字符个数 
	printf("请输入总共有多少个字符?");
	scanf("%d",&n);
	
	HuffNode * root;	// 完全二叉树的根节点 
	HuffNode ** F;		// 森林
	
	root = (HuffNode *)malloc(sizeof(HuffNode));
	F = (HuffNode **) malloc (n * sizeof(HuffNode *));
 
	// 初始化森林 
	for(i = 0;i < n; i++){
		F[i] = (HuffNode *)malloc (sizeof(HuffNode));
		
		fflush(stdin);
		printf("请输入第%d个字符:",i+1);
		scanf("%c", &w);
		F[i]->word = w;
		
		printf("请输入第%d个字符出现的频率:",i+1);
		scanf("%d", &ch);
		F[i]->weight = ch;
		
		F[i]->left = F[i]->right = NULL;
	}
	
	// 创建哈夫曼树
	root = CreatHuffmanTree(F,n); 
	// 先序遍历(测试输出) 
	OrderBy(root); 
	 
	
	return 0;
}
 
 
// 创建哈夫曼树 
HuffNode * CreatHuffmanTree(HuffNode ** F, int n)
{
	int loop;
	int min1,min2,i;
	for (loop = 0; loop < n-1; loop++)		// 注意:循环n-1次:完全二叉树层数n-1层。 
	{
		// 找最小值(min1)和次小值(min2)		
		for(min1 = 0; min1 < n && !F[min1]; min1++);		// 把min1放在第一个不为NULL的位置 
		for(min2 = min1+1; min2 < n && !F[min2]; min2++);	// 把min2放在第二个不为NULL的位置 
		for(i = min2; i < n; i++)
		{	
			if(F[i])		// 在F[i]不为空的前提下 
			{
				if(F[i]->weight < F[min1]->weight)
				{
					min2 = min1;
					min1 = i;
				}else if(F[i]->weight < F[min2]->weight){
					min2 = i;
				}	
			}
		} 
		
		// 合成新的二叉树,并放回去,默认:左子女是最小值,右子女是次小值,并且放回到最小值的位置
		HuffNode * p = (HuffNode *)malloc (sizeof(HuffNode));
		p->word = 'X';
		p->weight = F[min1]->weight + F[min2]->weight;
		p->left = F[min1];
		p->right = F[min2];
		F[min1] = p;
		F[min2] = NULL; 
	}
	return F[min1];
}
 
// 先序遍历二叉树 
void OrderBy(HuffNode * root)
{
	if(root)
	{
		printf("%d ",root->weight);
		OrderBy(root->left);
		OrderBy(root->right);
	} 
}

输出输出展示:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值