简介:
哈夫曼编码是贪心算法的应用,并可以得到最优解。
用于:压缩
传输举例:
传: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);
}
}
输出输出展示: