蓝桥杯 BASIC-28 Huffman树 POJ 3253 Fence Repair

【蓝桥杯】 BASIC-28 Huffman树 ( POJ 3253 Fence Repair )

POJ3253的题目背景其实就是哈夫曼树。

输入格式
  输入的第一行包含一个正整数n(n<=100)。
  接下来是n个正整数,表示p0, p1, …, pn-1,每个数不超过1000。
  
输出格式
  输出用这些数构造Huffman树的总费用。
  
样例输入

5
5 3 8 2 9

样例输出

59

题目分析
今天做了蓝桥杯官网基础练习里的这道题,题目描述就是Huffman树,大家在数据结构课上都已经学过了。要求输出构造哈夫曼树的总费用,即为所有非叶子结点的权值之和
之前ACM训练做过POJ 3253 Fence Repair这道题,其实只是修改了一下题目的描述背景,其实问题是一样的。

哈夫曼树的构造过程
在这里插入图片描述
在这里插入图片描述

这道题其实用优先队列就能解决了。每次从队列中取出最小的两个元素,将它们的和加入队列,直到队列当中只剩下最后一个元素,它的值就是我们要求的答案。
后来我又尝试建哈夫曼树来做,因为一开始只拿了90分,又发现了一些新的内容,下面会说明。

优先队列 priority_queue 解法】 100分代码:

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	priority_queue<int,vector<int>,greater<int> > q;//小的优先,注意“>”之间空格 
	int n,x;
	long long ans=0;
	cin>>n;
	while(n--)
	{
		cin>>x;
		q.push(x);
	}
	while(q.size()>1)
	{
		int t1,t2;
		t1=q.top();
		q.pop();
		t2=q.top();
		q.pop();
		ans+=t1+t2;
		q.push(t1+t2);
	}
	cout<<ans<<endl;
	return 0;
}

构造哈夫曼树的解法
我们知道,一棵n个叶子结点的哈夫曼树一共有2n-1个结点,输入的叶子结点为1到n号结点,将建树以后得到的第n+1到2n-1号结点权值求和,即为要求的答案。
这里我使在之前数据结构实验哈夫曼编码树的基础上进行修改,但一开始提交的时候发现只拿到了80分,有一组数据输出的结果和正确答案相差了一点点。我对Select函数稍加修改以后,提交拿了90分,但其中显示输出错误的那组样例我在本地测试得到的结果和答案是一样的,让我觉得有点奇怪。经过很长时间的思考并参考了他人的写法,发现是Select函数当中选择结点的时候存在小漏洞,如果样例的建树过程中存在两个或以上的权值相同的结点,程序运行时就有可能出现问题。
然后我采用以下正确的写法,提交100分通过,而且这样也能确保构造出的哈夫曼树具有相同的结构。代码当中给出了注释。

100分代码:
(注释部分为本题不需要用到的操作,以及之前写的存在漏洞的代码)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;

typedef struct
{
	int weight;
	int parent;
	int lchild;
	int rchild;
}HTNode,*HuffmanTree;//动态分配数组存储哈夫曼树 
typedef char** HuffmanCode;//动态分配数组存储哈夫曼编码表 

void Select(HuffmanTree &HT,int num,int &child1,int &child2)//注意Select函数的写法 
{
	int w1=1999999999,w2=1999999999;
	for(int i=1;i<=num;i++)
		if(HT[i].parent==0&&w1>HT[i].weight)
		{
			child1=i;
			w1=HT[i].weight;
			continue;
		}
	for(int i=1;i<=num;i++)
		if(HT[i].parent==0&&w2>HT[i].weight&&i!=child1)
		{
			child2=i;
			w2=HT[i].weight;
			continue;
		}
	//以上才是正确的Select写法,目的是使构造的哈夫曼树具有相同的结构 
	//之前做数据结构作业的时候写法如下,有小部分数据可能会出错,在蓝桥杯OJ上只能拿到90分 
	/* 
	child1=0;child2=0;
	int w1,w2;
	for(int i=1;i<=num;i++)
	{
		if(HT[i].parent==0)
		{
			if(child1==0||w1>=w2&&w1>=HT[i].weight)
			{
				child1=i;
				w1=HT[i].weight;
				continue;
			}
			if(child2==0||w2>=w1&&w2>=HT[i].weight)
			{
				child2=i;
				w2=HT[i].weight;
				continue;
			}
		}
	}
	*/
}//在HT[1]-HT[num]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2 

void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int w[],int n)
{
	int child1,child2;
	if(n<=1) return;
	int m=n*2-1;
	HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
	for(int i=1;i<=n;i++)
		HT[i]={w[i-1],0,0,0};
	for(int i=n+1;i<=m;i++)
		HT[i]={0,0,0,0};
	for(int i=n+1;i<=m;i++)
	{
		Select(HT,i-1,child1,child2);
		HT[child1].parent=i;
		HT[child2].parent=i;
		HT[i].lchild=child1;
		HT[i].rchild=child2;
		HT[i].weight=HT[child1].weight+HT[child2].weight;
	}//建哈夫曼树 
	/* 
	HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
	char* cd=(char *)malloc(n*sizeof(char));
	cd[n-1]='\0';
	for(int i=1;i<=n;i++)
	{
		int pos=n-1;
		for(int c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)
		{
			if(HT[f].lchild==c)
				cd[--pos]='0';
			else
				cd[--pos]='1';
		}
		HC[i]=(char *)malloc((n-pos)*sizeof(char));
		strcpy(HC[i],&cd[pos]);
	}//从叶子到根逆向求出每个字符的哈夫曼编码 
	*/
}

int main()
{
	int size,cnt[105];
	cin>>size;
	for(int i=0;i<size;i++)
		cin>>cnt[i];
	sort(cnt,cnt+size);
	HuffmanTree HT;//哈夫曼树 
	HuffmanCode HC;//哈夫曼编码表 
	HuffmanCoding(HT,HC,cnt,size);//进行哈夫曼编码操作 
	int ans=0;
	for(int i=size+1;i<=2*size-1;i++)
		ans+=HT[i].weight;
	cout<<ans<<endl;
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

球王武磊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值