注意事项:
- 哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的 路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL = (W1*L1 + W2 * L2 + W3 * L3 + … + Wn * Ln),N个权值Wi(i = 1, 2, …n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i = 1, 2, …n)。
- 观察a、b、c、d构成的编码树, 可以发现b之所以成为c的前缀, 是因为在这棵树上, b成为了c的父结点, 从在哈夫曼树当中, 原文档中的数据字符全都分布在这棵哈夫曼树的叶子位置, 从而保证了哈夫曼编码当中的任何一个字符的编码都不能是另一个字符编码的前缀.也就是说哈夫曼编码是一种前缀编码
例题:
哈夫曼算法:
1.根据给定的n个权值,构成n棵二叉树的集合f,其中每棵二叉树都只有一个带权的根结点,其左右子树为空。
2.将集合F中两棵权值最小的树组成一棵新二叉树,新树的权值为两棵树的权值之和。
3.删去这两棵树,同时将新树加入二叉树集合F中。
4.重复2、3,直到F中只含一棵树为止。
已知一段文本信息为“AFTER DATA EAR ARE ART AREA”,字符集为{A,E,R,T,F,D},请构建一颗哈夫曼树。
答:字符集共22个字符,其中各字符出现频率为A=8,E=4,R=5,T=3,F=1,D=1,将将频率设为其各字符的权值。以6个叶子结点分别代表A、E、R、T、F、D,构建哈夫曼树,且规定左分支代表字符‘0’,右分支代表字符‘1’,从根结点到叶子结点的路径上分支字符组成的字符串即为叶子结点的二进制前缀编码。
#include<stdio.h>
#include<malloc.h>
typedef struct
{
unsigned int weight;
unsigned int parent, lchild, rchild;//这是静态三叉链表二叉树结构。这种二叉树结构是动态生成的顺序结构
}HTNode, *HuffmanTree;
typedef char * *HuffmanCode;//动态分配数组存储赫夫曼编码表,指向指向char指针的指针
int min(HuffmanTree t, int i);
HuffmanCode HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC, int *w, int n);
int min(HuffmanTree t, int i)//返回树的前i个结点中权值最小的树根结点序号
{
int j, m;
unsigned int k = 100;//初值取为大于所有可能的值
for (j = 1; j <= i; j++)//对于前i个结点不断迭代找到最小值,不断覆盖最小值标号m
if (t[j].weight < k && t[j].parent == 0)
{
k = t[j].weight;
m = j;
}
t[m].parent = 1;//给选中的根结点的双亲赋0值,避免第二次查找该节点,相当于删掉在集合F中的结点
return m;
}
int main()
{
HuffmanTree HT; //赫夫曼头结点,因为是静态二叉树,0号不存储值,当做头结点使用。
HuffmanCode HC;
HuffmanTree p; //看做在赫夫曼树中遍历的指针
int *w, m, n, i, s1, s2, temp;
printf("请输入权值的个数(>1):");
scanf_s("%d", &n);
w = (int*)malloc(n * sizeof(int)); //动态生成存放n个权值的空间
printf("请依次输入%d个权值(整数):\n", n);
for (i = 0; i <= n - 1; i++)
scanf_s("%d", w + i);
m = 2 * n - 1; //n个叶子结点的赫夫曼树有m个结点
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //0号结点建立但不使用,故建立m+1个
for (p = HT + 1, i = 1; i <= n; i++, p++, w++) //将1号到n号单元,给叶子结点赋值
{
(*p).weight = *w;
(*p).parent = 0;
(*p).lchild = 0;
(*p).rchild = 0;
}
for (;i <= m;i++, p++) //只将n+1到m号结点的双亲域赋0
(*p).parent = 0;
for (i = n + 1;i <= m; i++) //n+1到m号结点属于非叶子结点,他们负责将叶子结点按照算法串联
{
s1 = min(HT, i - 1);//因为使用第i号结点作为最小结点的和的双亲结点,所以每次遍历都会将i加入到二叉树集合F中
s2 = min(HT, i - 1);
if (s1 > s2)
{
temp = s1;
s1 = s2;
s2 = temp;
}
HT[s1].parent = HT[s2].parent = i;//将s1和s2的双亲设为i
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
HC = HuffmanCoding(HT, &HC, w, n);//根据w所存的n个权值来构建赫夫曼树HT,n个赫夫曼树存于HC
for (int i = 1; i <= n; i++)
puts(HC[i]);
return 0;
}
HuffmanCode HuffmanCoding(HuffmanTree HT, HuffmanCode *HC, int *w, int n)//根据赫夫曼树建立赫夫曼编码的过程
{ //从叶子到根逆向求每个字符的赫夫曼编码
int start;
unsigned f, c;
char *cd;
*HC = (HuffmanTree)malloc((n+1) * sizeof(char*));//分配n个字符编码的头指针向量([0]不用)
cd = (char*)malloc(n * sizeof(char));//分配求编码时的工作空间。因为字符数绝不会大于叶子数,所以以其作为最大值
cd[n - 1] = '\0';
for (int i = 1;i <= n;i++)//选定从第一个结点开始,判定它属于左孩子还是右孩子,然后在向上层继续循环
{
start = n - 1;//逆序将编码存储,故从结束符位置开始。
for (c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
if (HT[f].lchild == c)//每次判断属于左孩子还是右
cd[--start] = '0';
else
cd[--start] = '1';
(*HC)[i] = (char*)malloc((n - start) * sizeof(char));//根据每个结点的编码长度,动态分配空间
strcpy((*HC)[i], &cd[start]); //HC指向char指针的地址,HC[i]代表第i结点的编码,&cd[]是逆序的字符串
}
free(cd);
return *HC;
}