一、前导概念
1.节点 i 的路径长度:从根节点到节点 i 的路径上所经过的边数。
2.树的路径长度:(版本一)所有节点的路径长度之和为该树的路径长度
(版本二)所有叶子节点的路径长度
内部路径长度:所有内部节点(除叶子节点外的节点)的路径长度
外部路径长度(在一些资料中被称为树的路径长度):所有叶子节点的路径长度
上图树的路径长度为:1*3+2*6+ 3*1=18,内部路径长度为5,外部路径长度为13
3.节点 i 的带权路径长度:节点 i 的路径长度 * 节点权值
4.树的带权路径长度(WPL):所有叶子节点的带权路径长度之和
给出n个节点(都带有权值),可以再加入若干节点(不确定的),现在去建立一棵树,用这n个节点全部做叶子节点,自行加入的节点做内部节点去建立一棵二叉树,其中WPL最小的一棵二叉树称为哈夫曼树(最优二叉树)
哈夫曼树的WPL值唯一
哈夫曼树样子不唯一
二、构建哈夫曼树
对于给定的n个节点(带权值且只做叶子节点),构建哈夫曼树
树的合并一开始认为这n节点是n棵树:
每次找根节点权值最小的两棵树(x,y),再新加入一个节点做x和y节点的父亲,新节点的权值为(x+y),此时将x y两棵树合并出了一棵,这棵树的根节点就是z(把该过程重复n-1次)
即可得到哈夫曼树
三、哈夫曼编码
应用场景:通过编码进行数据压缩:哈夫曼编码
1.引子
假设有一种编码方式中每个字符使用2个bit
可以画出下面的编码树
00 A
01 B
10 C
11 D
把每个字符出现的频率看成节点的权值,则该树的WPL值为消息的位数
例如,上图中ABCAAD 可以画出下图
可以看出该消息的位数为(3+1+1+1)*2=12
让字符所在节点做叶子节点,如何使这条消息的编码最小,即如何使这条消息的WPL最小
----> 使用哈夫曼树来构造变长编码
2.如何使用哈夫曼树来构造变长编码
(1)先统计一条消息中每个字符的出现次数(频率)---->节点权值
(2)构造哈夫曼树
(3)给父亲节点的两条边标1,0
3.前缀属性原则
短的编码不能是长编码的前缀
例如:
A:001
B: 0
C: 01
此时无法判断 001表示的是A还是BC
哈夫曼编码一定满足前缀属性原则:长编码的前缀不可能是叶子节点
4.建立哈夫曼树
(1)把每个字符的频率看成节点的权值,建立哈夫曼树
1.1)查找最小的两个根节点
1.2)合并;加入一个新的根节点
用什么数据结构存储树?----->结构体数组模拟树
结点中要存储的信息:权值,父亲的下标,左右孩子节点的下标
如何判断节点是根节点--->父亲节点的下标==本身的下标
5.代码(构建哈夫曼树+哈夫曼编码)
#include <stdlib.h>
#include <stdio.h>
# include <string.h>
//数组模拟树
typedef struct {
int w;//Ȩ权值
int f;//父亲节点的下标
int l,r;//左右指针的下标
}HuffmanNode,*HuffmanTree;
void find(HuffmanTree t,int x,int* w1,int* w2)
{
//先找最小
int minn=0;//最小值的下标
for(int i=1;i<=x;i++)
{
if(t[i].f==i)
{
minn=i;
break;
}
}
for(int i=1;i<=x;i++)
{
if(t[i].f ==i)
{
if(t[i].w<t[minn].w)
{
minn=i;
}
}
}
*w1=minn;//最小的根节点下标保存至w1中
//找第二小
for(int i=1;i<=x;i++)
{
if(t[i].f==i&&i!=(*w1))
{
minn=i;
break;
}
}
for(int i=1;i<=x;i++)
{
if(t[i].f==i&&i!=(*w1))
{
if(t[i].w<t[minn].w)
{
minn=i;
}
}
}
*w2=minn;
}
HuffmanTree createHuffmanTree(int *wi,int n)
{
int m=2*n;//从1开始,(2n-1)个数组
HuffmanTree t=(HuffmanTree)malloc(sizeof(HuffmanNode)*m);
for(int i=1;i<m;i++)
{
t[i].f=t[i].l =t[i].r=0;
t[i].w=0;
}
for(int i=1;i<=n;i++)
{
t[i].w=wi[i-1];//t数组从1开始,w数组从0开始
t[i].f=i;
}
int w1,w2;//权值最小的两个根节点的下标
for(int i=n+1;i<m;i++)
{
find(t,i-1,&w1,&w2);//地址传参
t[w1].f=t[w2].f=i;
t[i].f=i;
t[i].w=t[w1].w+t[w2].w;
t[i].l=w1;
t[i].r=w2;
}
return t;
}
char** ctreateHuffmanCode(HuffmanTree t,int n)
{
char *temp=(char*)malloc(sizeof(char)*n);//暂存数组,下标:0~~n-1
char **code=(char**)malloc(sizeof(char*)*n);//最终保存数组
int start;//一开始放编码的位置
int pos,p;
for(int i=1;i<=n;i++)
{
start=n-1;
temp[start]='\0';
pos=i;
p=t[pos].f;//p是pos的父亲
while(t[pos].f!=pos)
{
start--;
if(t[p].l==pos)
{
temp[start]='0';
}else{
temp[start]='1';
}
pos=p;
p=t[pos].f;
}
code[i-1]=(char*)malloc(sizeof(char)*(n-start));
strcpy(code[i-1],&temp[start]);
}
free(temp);
temp=NULL;
return code;
}
int main()
{
char s[8] = {'A', 'B', 'C', 'D','E', 'F', 'G', 'H'};
int w[8] = {5, 29, 7, 8,14, 23, 3, 11};
HuffmanTree tree = createHuffmanTree(w,8);
char **code= ctreateHuffmanCode(tree,8);
for(int i=0;i<8;i++)
{
printf("%c : %s\n",s[i],code[i]);
}
return 0;
}