PTA习题解析——修理牧场(2 种解法)

禁止码迷,布布扣,豌豆代理,码农教程,爱码网等第三方爬虫网站爬取!

题目详情#

题干#

输入样例#

Copy Highlighter-hljs
8 4 5 1 2 1 3 1 1

输出样例#

Copy Highlighter-hljs
49

哈夫曼树#

情景模拟#

我们如果按照分割木块的思想去分析,会显得很不直观,所以我们直接通过我们的目标来组合出最花费的情况,那么这就很显然要使用贪心算法来解决。如图所示用圆形来表示小木块。

要保证花费更小,就需要让较短的木块后生成,较长的木块先生成,因此我选择较小的两个木块,还原为它们被分割出来之前的状态。即选择两个长度为 1 的木块,组合成一个长度为 2 的木块。

接下来我要去生成下一个木块,选择两个目前最短的木块,还是两个 1 长度的木块,组合成长度为 2 的木块。

重复上述操作。





模拟完毕,观察一下我们发现,我们已经还原出了木块的每一次应该如何切割,由于木块切割费用与其长度相等,因此我们把黄色源泉的数字都加起来,发现正好是 49。此时我可以说,问题已经解决了,我们把目标木块当成一个个结点,长度当做权,这道题不就是要建一棵哈夫曼树嘛。

代码实现#

将上述函数封装好,并添加读取数据的代码即可实现。

Copy Highlighter-hljs
#include<iostream> using namespace std; typedef struct { int weight; //权值 int parent, lchild, rchild; //指向双亲、左右孩子结点的游标 }HTNode, * HuffmanTree; void CreatHuffmanTree(HuffmanTree& HT, int n); //建哈夫曼树 void Select(HuffmanTree HT, int k, int &idx1, int &idx2); //找孩子结点 int main() { int n; HuffmanTree HT; int sum = 0; cin >> n; CreatHuffmanTree(HT, n); //建哈夫曼树 for (int i = n + 1; i < 2 * n; i++) //算总花费 { sum += HT[i].weight; } cout << sum; } void CreatHuffmanTree(HuffmanTree& HT, int n) { int m = 2 * n - 1; int idx1, idx2; int i; if (n < 0) //树的结点数为 0,即空树 return; //二叉树初始化 HT = new HTNode[m + 1]; //0 号元素不使用,因此需要分配 m + 1 个单元,HT[m] 为根结点 for (i = 1; i <= m; i++) { HT[i].parent = HT[i].lchild = HT[i].rchild = 0; //初始化结点的游标均为 0 } for (i = 1; i <= n; i++) cin >> HT[i].weight; //输入各个结点的权值 //建哈夫曼树 for (i = n + 1; i <= m; i++) { Select(HT, i , idx1, idx2); //在 HF[k](1 <= k <= i - 1)中选择两个双亲游标为 0 且权值最小的结点,并返回他们在 HT 中的下标 idx1,idx2 HT[idx1].parent = HT[idx2].parent = i; //得到新结点 i,idx1 和 idx2 从森林中删除,修改它们的双亲为 i HT[i].lchild = idx1; HT[i].rchild = idx2; //结点 i 的左右子结点为 idx1 和 idx2 HT[i].weight = HT[idx1].weight + HT[idx2].weight; //结点 i 的权值为左右子结点权值之和 } } void Select(HuffmanTree HT, int k, int& idx1, int& idx2) { int min1, min2; min1 = min2 = 99999999999; //不能太小 for (int i = 1; i < k; i++) { if (HT[i].parent == 0 && min1 > HT[i].weight) { if (min1 < min2) { min2 = min1; idx2 = idx1; } min1 = HT[i].weight; idx1 = i; } else if (HT[i].parent == 0 && min2 > HT[i].weight) { min2 = HT[i].weight; idx2 = i; } } }

优先级队列(非 STL 库)#

情景模拟#

为了使费用最省,我们使用贪心算法的思想,每一次选择最小的两段木头拼回去,直到将所有木头拼成一段完整的木头,每次一拼接都计算一次费用。我们发现,优先级队列也是可以实现贪心算法的。

我们首先需要先把这个队列修改成小顶堆,方便我们实现优先级队列。

接下来令两个元素出队列,计算一次费用,然后将两个元素之和的数字入队列。

重复上述操作,使的队列只剩一个元素。





解法分析#

在这里我们可以看出优先级队列是可以解决问题的,此时的问题是我该怎么控制堆中的数据元素个数?通过观察,每一次是出堆 2 个元素,入堆 1 个元素,也就是说每次的净出堆元素是 1 个,那么就在堆中元素为 1 时结束这个流程就行了。
接下来就是如何调整堆的问题了,比较粗暴的方式是每次更改之后都建初堆,但是这样效率和哈夫曼树差不多,优化不是很明显。根据我们刚刚的分析,既然有 2 次出堆,那么在出堆之后保证剩下的元素也是堆就可以了,那么只需要 2 次调整堆。比较方便的操作是第一次出堆时,拿堆的最后一个元素到堆顶调整堆,第二次出堆时直接把入堆元素填充到堆顶调整堆。

代码实现#

Copy Highlighter-hljs
#include<iostream> using namespace std; void heapify(int a_heap[], int idx1, int idx2); void creatHeap(int a_heap[], int n); int main() { int a_heap[10001]; int count; //堆中剩余元素个数 int heap_top; //暂时保存第一个堆顶 int money = 0; //总费用 cin >> count; for (int i = 1; i <= count; i++) { cin >> a_heap[i]; } creatHeap(a_heap, count); //建初堆 while (count != 1) { heap_top = a_heap[1]; //提取第一个最小花费 a_heap[1] = a_heap[count--]; heapify(a_heap, 1, count); //调整堆寻找第二个最小花费 a_heap[1] += heap_top; //提取第二个最小花费,将花费相加,入堆 money += a_heap[1]; //更新总花费 heapify(a_heap, 1, count); // 调整堆,准备下一次拼接 } cout << money; return 0; } void heapify(int a_heap[], int idx1, int idx2) //调整堆,细节见上文 { int insert_node = a_heap[idx1]; for (int i = 2 * idx1; i <= idx2; i *= 2) { if (i < idx2 && a_heap[i] > a_heap[i + 1]) { i++; } if (insert_node <= a_heap[i]) { break; } a_heap[idx1] = a_heap[i]; idx1 = i; } a_heap[idx1] = insert_node; } void creatHeap(int a_heap[], int n) //建初堆,细节见上文 { for (int i = n / 2; i > 0; i--) { heapify(a_heap, i, n); } }

延伸阅读#

哈夫曼树与哈夫曼编码
堆、优先级队列和堆排序

posted @   乌漆WhiteMoon  阅读(1642)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
CONTENTS