原理:
严版教材p144,真题册P163
算法步骤:
(1) 赫夫曼树的创建
1)定义赫夫曼树的存储结构,包含指向双亲结点、左右孩子结点的指针、和权值信息;
2)定义一个返回HTNode*类型的函数,用来创建赫夫曼树,闯入的参数为叶子结点n,和权值数组w;
3)根据叶子结点个数确定赫夫曼树结点的个数m,然后定义一个结点指针数组HTNode* HT,作为赫夫曼树;
4)将赫夫曼树初始化,叶结点权值为权值数组中对应的权值,非叶子结点权值为0,所结点的双亲结点和孩子结点指针都为0,此时可以将每个结点看作一棵独立的树;
5)构建赫夫曼树,填充非叶子节点。做一个循环,从n+1到m,遍历每一个非叶子节点:
对于一个非叶子节点i(此时因为还没有填充该结点,应该算作一棵独立的树),从还未并入赫夫曼树的树中选择两棵头结点权值最小的树(假设它们在数组中的下标为s1、s2)
令i的左孩子指向s1,右孩子指向s2,权值为s1和s2两个结点的权值之和
再删除这两个结点,使它们的双亲结点指针为1;
6)循环结束,返回HTNode*类型的指针变量HT。
补充:
关于选择权值最小的树的算法:
1)构造一个空值函数,传入的参数为赫夫曼树结点指针数组HT,数组大小n(范围为1到i-1,有权值的结点),和两个int*的指针变量s1、s2;
2)定义一个min变量,用来寻找最小权值的数组元素下标,给min赋初值。做一个循环先找到第一个最小权值的数组元素,这个结点的双亲结点为0表示还没有并入赫夫曼树;
3)打擂台法确定最小值。做一个循环,从第一个元素遍历到最后一个元素,如果该元素双亲结点为0且权值比当前最小值结点权值要小就将该元素下标赋值给min。循环结束将min赋值给*s1;
4)确定第二个变量*s2的值。与第二、三步相同,限制条件为选中的结点下标不能等于*s2。
(2)赫夫曼编码以及计算wpl
1)构造一个返回char**的函数,传入的参数为赫夫曼树即HTNode HT[],以及叶结点的个数n和带权路径长度变量int*wpl;
2)创建一个存储赫夫曼树编码串的数组,大小为n+1,代表在1到n的位置存储n个字符串,类型为char**;
3)创建一个临时字符串数组,大小为n,用来保存单个字符串编码,并在末尾设置结束符‘\0’;
4)下面做两个循环,第一个循环用来遍历每一个叶结点:
取一个叶结点,设置father变量指向双亲结点下标,current变量指向当前结点下标,变量start标记临时数组法当前字符的下,每次循环默认为n-1,level计算路径长度,默认为0;
5)第二个循环,从叶结点回溯至根节点,求取路径,判断双亲结点指针father是否为0,等于0时结束循环:
判断当前结点是双亲结点的左孩子还是右孩子,如果为左孩子则临时数组赋值'0',否则为1;
将father赋值给current,current的双亲结点下标赋值给father,同时level自加1;
6)继续第一个循环,用2)创建的指针数组中取一个元素存储创建一个大小为n-start的字符串数组的地址,然后将临时数组中start开始的字符赋值给这个刚刚创建的字符串数组,再用level计算带权路径长度*wpl。
7)第一个循环结束后,将临时数组空间释放掉,返回指针数组的数组名。
改编代码(纯C):
#include
#include
#include
typedef struct
{
int parent;
int lchild;
int rchild;
int weight;
}HTNode, *HuffmanTree;
typedef char** HuffmanCode;
void Select(HuffmanTree HT, int n, int* s1, int* s2)
{
int i, min;//min用来临时存储最小值节点的下标
//给min赋初值
for (i = 1; i <= n; ++i)
{
if (HT[i].parent == 0)
{
min = i;
break;
}
}
//寻找第一个最小值结点
for (i = 1; i <= n; ++i)
{
if (HT[i].parent == 0)
if (HT[i].weight < HT[min].weight)
min = i;
}
*s1 = min;
//给min赋初值,且与s1不同
for (i = 1; i <= n; ++i)
{
if (HT[i].parent == 0 && i != *s1)
{
min = i;
break;
}
}
//寻找第二个最小值结点
for (i = 1; i <= n; ++i)
{
if (HT[i].parent == 0 && i != *s1)
if (HT[i].weight < HT[min].weight)
min = i;
}
*s2 = min;
}
HuffmanTree CreateHuff(int n, int* w)
{
int i, m = 2 * n - 1;//叶子结点为n的哈夫曼树有m个结点
HuffmanTree HT = (HTNode*)malloc((m + 1) * sizeof(HTNode));//0号单元未用
//叶子结点初始化
for (i = 1; i <= n; ++i, ++w)
{
HT[i].weight = *w;
HT[i].lchild = 0;
HT[i].rchild = 0;
HT[i].parent = 0;
}
//非叶子结点初始化
for (i = n + 1; i <= m; ++i)
{
HT[i].weight = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
HT[i].parent = 0;
}
//建哈夫曼树
for (i = n + 1; i <= m; ++i)
{
int s1, s2;
//选择两棵根结点权值最小的树作为新结点的左右子树
Select(HT, i - 1, &s1, &s2);
//对新节点进行赋值
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
//删除刚才选出的两个节点
HT[s1].parent = i;
HT[s2].parent = i;
}
return HT;
}
//哈夫曼编码并计算wpl
HuffmanCode HuffmanCoding(HTNode HT[], int n, int* wpl)
{
HuffmanCode HC = (char**)malloc((n + 1) * sizeof(char*));//分配n个指向哈夫曼编码串的指针,0号单元未用
char* cd = (char*)malloc(n * sizeof(char));//创建一个临时字符串数组,存储单个叶结点的哈夫曼编码串,正则二叉树的最大高度是n
cd[n - 1] = '\0';//字符数组最后一个位置设置结束符
*wpl = 0;//赋初值
int i, start;
int current;//标记当前结点
int father;//标记当前结点的父结点
int level;//用来存储路径长度
//逐字符求哈夫曼编码
for (i = 1; i <= n; i++)
{
start = n - 1;//编码结束符位置,start必须在循环内
current = i;
father = HT[current].parent;
level = 0;
//从叶子节点到根逆向求编码
while (father != 0)
{
if (HT[father].lchild == current)//注意这里写错过,写成了i
cd[--start] = '0';
else
cd[--start] = '1';
current = father;
father = HT[current].parent;
++level;
}
HC[i] = (char*)malloc((n - start) * sizeof(char));/*简记:结束符的下标为n-1,从结束符到下标为start的字符个数为n-1 - start + 1,即n-start*/
strcpy(HC[i], &cd[start]);
*wpl += level * HT[i].weight;
}
free(cd);//用完以后不要忘记释放工作空间
return HC;
}
int main()
{
int i, n, wpl;
printf("Please input n:");
scanf("%d", &n);
int* w = (int*)malloc(n * sizeof(int));
for (i = 0; i < n; i++)
{
printf("Please input %dth weight:", i+1);
scanf("%d", &w[i]);
}
HuffmanTree HT = CreateHuff(n, w);
HuffmanCode HC = HuffmanCoding(HT, n, &wpl);
//输出树的带权路径长度
printf("WPL=%d\n", wpl);
//输出编码
for (i = 1; i <= n; ++i)
{
printf("%dth:%s\n", i, HC[i]);
}
system("pause");
}