zoukankan      html  css  js  c++  java
  • 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);
        }
    }
    

    延伸阅读

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

  • 相关阅读:
    腾讯蓝鲸cmdb部署
    Linux 检测 tls
    MongoDB主从
    rundeck配置salt-api
    salt-api 配置使用
    rsync同步备份
    su与su -,sudo 的区别
    Redis 迁移 DB; move key db
    数据结构与算法面试题80道(36)
    数据结构与算法面试题80道(35)
  • 原文地址:https://www.cnblogs.com/linfangnan/p/12951382.html
Copyright © 2011-2022 走看看