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

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

题目详情

题干

输入样例

8
4 5 1 2 1 3 1 1

输出样例

49

哈夫曼树

情景模拟

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

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

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

重复上述操作。





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

代码实现

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

#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 次调整堆。比较方便的操作是第一次出堆时,拿堆的最后一个元素到堆顶调整堆,第二次出堆时直接把入堆元素填充到堆顶调整堆。

代码实现

#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);
    }
}

延伸阅读

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

原文地址:https://www.cnblogs.com/linfangnan/p/12951382.html