哈夫曼树算法思想设计内线电话号码

前言

一个单位有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。

在这里插入图片描述

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值