前言
一个单位有12个部门,每个部门都有一部电话,但是整个单位只有一根外线,当有电话打过来的时候,由转接员转到内线电话,已知各部门使用外线电话的频率为(次/天):5 20 10 12 8 43 5 6 9 15 19 32。
利用哈夫曼树算法思想设计内线电话号码,使得接线员拨号次数尽可能少。
分析
哈夫曼使用自底向上的方法构建二叉树,避免了次优算法Shannon-Fano编码的最大弊端──自顶向下构建树。
同一符号可以有不同的码长,即编码方法并不唯一,其原因是两支路概率合并后重新排队时,可能出现几个支路概率相等,造成排队方法不唯一。一般,若将新合并后的支路排到等概率的最上支路,将有利于缩短码长方差,且编出的码更接近于等长码
赫夫曼编码的具体方法:先按出现的概率大小排队,把两个最小的概率相加,作为新的概率 和剩余的概率重新排队,再把最小的两个概率相加,再重新排队,直到最后变成1。每次相加时都将“0”和“1”赋与相加的两个概率,读出时由该符号开始一直走到最后的“1”, 将路线上所遇到的“0”和“1”按最低位到最高位的顺序排好,就是该符号的赫夫曼编码。
哈夫曼编码可以从叶子节点开始到根节点逆向求哈弗曼编码,当然也可以从根结点开始到叶子结点正向求哈夫曼编码.
代码部分
//一个单位有12个部门,每个部门都有一部电话,但是整个单位只有一根外线,当有电话打过来的时候,
//由转接员转到内线电话,已知各部门使用外线电话的频率为(次/天):5 20 10 12 8 43 5 6 9 15 19 32。
//由哈夫曼编码算法实现
//哈夫曼编码可以从叶子结点开始到根节点逆向求哈夫曼编码,
//当然也可以从根节点开始到叶子结点正向求哈夫曼编码,
//本章采用逆向求哈夫曼编码
#define OK 1
#define ERROR 0
#pragma warning( disable : 4996) //忽略strcpy_s()函数替换提示
#include <stdio.h> //引入库文件
#include<string.h>
#include<stdlib.h>
#include<malloc.h>
#define infinity 10000 //定义一个无限大的值
typedef int status;
typedef struct /*哈夫曼树类型定义*/
{
unsigned int weight;//权值
unsigned int parent, lchild, rchild; //unsigned:关键字表示无符号整型,只能用于表示零和正整数
} HTNode, * HuffmanTree;
/*char *字符型指针,指向一个字符;
char ** 指向字符型指针的指针;*/
typedef char** HuffmanCode;
int Min(HuffmanTree t, int n);
void Select(HuffmanTree* t, int n, int* s1, int* s2);
void HuffmanCoding(HuffmanTree* HT, HuffmanCode* HC, int* w, int n);
int main() {
HuffmanTree HT;
HuffmanCode HC;
int* w, i;
int n = 0;
char* cd;
printf("请输入叶子结点的个数:");
scanf_s("%d", &n);
w = (int*)malloc(n * sizeof(int)); //为n个结点的权值分配内存空间
for (i = 0; i < n; i++) {
printf("请输入第%d个结点的权值:", i + 1);
scanf_s("%d", w + i);
}
HuffmanCoding(&HT, &HC, w, n);
for (i = 1; i <= n; i++) {
printf("哈夫曼编码:");
puts(HC[i]);
}
//释放内存空间
for (i = 1; i <= n; i++)
free(HC[i]);
free(HC);
free(HT);
return OK;
}
//构造哈夫曼树HT,哈夫曼树的编码存放在HC中,w为n个字符的权值
void HuffmanCoding(HuffmanTree* HT, HuffmanCode* HC, int* w, int n)
{
int m, i, s1, s2, start;
unsigned int c, f;
HuffmanTree p;
char* cd;
if (n <= 1) //哈夫曼编码是解决多个字符使用权重不同时,即如果只有1个或0个字符,没必要使用哈夫曼编码,
return;
m = 2 * n - 1; //观察哈夫曼树可知,n个不同权重的字符,有n个叶子结点,非叶子结点有n-1个,总共的结点数目为2*n-1个
*HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//第0个单元未用
for (p = *HT + 1, i = 1; i <= n; i++, p++, w++) { //初始化n个叶子结点
(*p).weight = *w;
(*p).parent = 0;
(*p).lchild = 0;
(*p).rchild = 0;
}
for (; i <= m; i++, p++) { //将n-1个非叶子结点的双亲结点初始化为0
(*p).parent = 0;
}
for (i = n + 1; i <= m; ++i) { //构造哈夫曼数
Select(HT, i - 1, &s1, &s2); //查找树中权值最小的两个结点
(*HT)[s1].parent = (*HT)[s2].parent = i;
(*HT)[i].lchild = s1;
(*HT)[i].rchild = s2;
(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
}
//从根结点求每个字符的哈夫曼编码
*HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
cd=(char*)malloc(n*sizeof(char)); //为哈夫曼编码动态分配空间
cd[n-1]='\0';
//求n个叶子结点的哈夫曼编码
for(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));//为第i个字符编码分配空间
strcpy((*HC)[i],&cd[start]);
}
free(cd);
}
//查找权值最小和次小的两个结点
int Min(HuffmanTree t, int n) {
//返回树中n个结点中权值最小的节点序列
int i, flag;
int f = infinity; //f为一个无限大的值
for (i = 1; i <= n; i++) {
if (t[i].weight < f && t[i].parent == 0) {
f = t[i].weight;
flag = i;
}
}
t[flag].parent = 1;//每次选两个小的结点,先给选中的结点双亲结点赋值1作为标记
//parent分三种情况,已经有parent结点,指向parent结点
//没有parent结点,依旧是初始的值0
//找到最小的先赋值1标记
return flag;
}
//在n个结点中选择两个权值最小的结点序号,其中s1最小,s2次小
void Select(HuffmanTree* t, int n, int* s1, int* s2) {
int x;
*s1 = Min(*t, n);
*s2 = Min(*t, n);
//如果序号s1的权值大于序号s2的权值,将两者交换,使s1最小,s2次小
if ((*t)[*s1].weight > (*t)[*s2].weight) {
x = *s1;
*s1 = *s2;
*s2 = x;
}
}
测试结果
一个单位有12个部门,各部门使用外线电话的频率为(次/天):5 20 10 12 8 43 5 6 9 15 19 32。