zoukankan      html  css  js  c++  java
  • 算法第四章上机实践报告

    引论:相比与动态规划算法,贪心算法是比较容易理解的,其思想就在于得到当前状态下局部最好选择,当一个问题的最优解包含其子问题的最优解时,即每个贪心选择都是子问题的最优解,那么就能的到该问题的最优解了。本次上机实践的题目虽然不是特别难,但相比前两次,这一次上机实践的效率远低于上两次,因为在实践的时候被第二题难住了,就没有去思考第三题,导致进度大大落后。既然如此,那么本篇博客就以第三题为例来讲解贪心算法吧。

    一、实践题目

    给定k 个排好序的序列, 用 2 路合并算法将这k 个序列合并成一个序列。 假设所采用的 2 路合并算法合并 2 个长度分别为m和n的序列需要m+n-1 次比较。试设 计一个算法确定合并这个序列的最优合并顺序,使所需的总比较次数最少。 为了进行比较,还需要确定合并这个序列的最差合并顺序,使所需的总比较次数最多。

    输入格式:

    第一行有 1 个正整数k,表示有 k个待合并序列。 第二行有 k个正整数,表示 k个待合并序列的长度。

    输出格式:

    输出最多比较次数和最少比较次数。

    输入样例:

    在这里给出一组输入。例如:

    4
    5 12 11 2 

    输出样例:

    在这里给出相应的输出。例如:

    78 52
    

      

    二、问题描述

    要解决本题,首先要理解题目中提及的 2 路合并算法,其意思是将含m和n个数字的数组合并,则需要m + n - 1 次比较,得到的是一个含m + n个数字的新数组。给出了k个数组,那么就要进行k - 1次2路合并,例如题目的输入样例给出了4个数组,那么就需要进行3次合并。而又因为各数组的数字数目不同,则合并时进行的比较次数也不同,因此需要找到一个合适的合并顺序使得总比较次数最少和最多。所以,本题的解题关键是:找到合并数组的顺序!

    三、算法描述

    首先列出我解题的代码

     1 #include <iostream>
     2 #include <queue> 
     3 using namespace std;
     4 
     5 int main()
     6 {
     7     int n;        //需二路排序的序列数目
     8     cin >> n;
     9     const int num = n; 
    10     int array[num];        //各序列的数字数目 
    11     
    12     for (int i = 0; i < n; i++)
    13     {
    14         cin >> array[i];
    15     }
    16     
    17     priority_queue <int, vector<int>, greater<int> > tempQueue;        //定义小顶堆 
    18     priority_queue <int> largeQueue;                                //定义大顶堆 
    19     
    20     for (int i = 0; i < n; i++)        //存数组中的数字入堆 
    21     {
    22         tempQueue.push(array[i]);
    23         largeQueue.push(array[i]); 
    24     }
    25     
    26     
    27     
    28     int min = 0;        //min表示最少比较总次数 
    29     int max = 0;         //max表示最多比较总次数
    30     
    31     while (true)            //求最少次数 
    32     {    
    33         int sum = 0;                    //sum表示二路合并后的值
    34         int temp = tempQueue.top();        //堆顶元素存入temp中
    35         tempQueue.pop();
    36         if (!tempQueue.empty())
    37         {
    38             sum = temp + tempQueue.top(); 
    39             min += (temp + tempQueue.top() - 1);
    40             tempQueue.pop();
    41             tempQueue.push(sum);
    42         }
    43         else
    44         {
    45             break;
    46         }
    47     }
    48     
    49     while (true)            //求最多次数 
    50     {    
    51         int sum = 0;                    //sum表示二路合并后的值
    52         int temp = largeQueue.top();        //堆顶元素存入temp中
    53         largeQueue.pop();
    54         if (!largeQueue.empty())
    55         {
    56             sum = temp + largeQueue.top(); 
    57             max += (temp + largeQueue.top() - 1);
    58             largeQueue.pop();
    59             largeQueue.push(sum);
    60         }
    61         else
    62         {
    63             break;
    64         }
    65     }
    66     
    67     cout << max << " " << min;
    68     return 0;
    69 }

     题目要求是求出最少和最多的总比较次数,结合我们求哈夫曼树的经验,那么如果想得到最少的总比较次数,那么就需要选出给定的最短的数组,先进行二路合并,得到新数组,再比较新数组和其余数组,再次挑出2个最短的数组,直到只剩下一个数组即为最少总比较次数数组。相反,若想得到比较次数最多,那么我们按照相同的思想,只需要把挑出两个最短的改为挑出两个最长的,直到只剩下一个数组即为最多总比较次数数组。恰好这个解题思路很适合使用<queue>头文件中内置的数据结构——优先队列priority_queue

    priority_queue <int> (变量名)    //默认为最小的元素为堆顶

    当我们让优先队列的头元素出队列时,优先队列是自动排好序的,即保持最小的元素保持在队头,这样我们每次只需要取出队列头元素,并出队列,再取出第二个队列头,进行二路合并,即能达成我们保持合并两个最短队列的思想。

    下面用反证法来证明最优子结构策略:(由于求出最少比较次数与最多比较次数类似,所以这里以全球除最少比较次数为例)

    设a 与 b 数组是给出的最短数组,其数目分别为m 与 n,合并两个数组的比较次数为 m + n - 1, 得出的新数组的长度为 m + n。 若还有两个数组c 与 d , 他们的数目分别为i 与 j,合并次数为 i + j - 1, 则得出的数组长度为i + j。 而整个问题的最优解还需要在长度为i + j的数组与剩余的数组(设长度为k)二路合并,但由于数组a 与b是原有的最短的数组, 即 m + n - 1 < i + j - 1,且 m + n + k < i + j + k,则导致求出的最优解不是整个问题的最优解,与求出总最少比较次数矛盾。(大问题的最优解包含子问题的最优解)

    用反证法来证明贪心选择:

    先挑选出原有数组的最短的两个数组(长度为 n 与 m),比较次数为C1 = n + m - 1,合并后的数组长度为(n + m);再挑选出剩下的数组和长度为n + m的数组中最短的两个进行二路合并, 若第二次合并是长度为i的数组与第一次合并得出的数组合并,则比较次数C2 = n + m + i - 2。若有两个数组合并后的长度为 k < n + m,合并它所比较的次数为 q < n + m - 1,第二次合并的比较次数为 q + i,那么总比较次数包含 q + i,但由于不存在该两个数组,得出矛盾。所以本问题的最优解是通过先求出n + m - 1 再得出 n + m + i - 2,再一步步就能得出大问题的最优解。(整体最优解可以通过一系列局部最优的选择)

    四、算法时间及空间复杂度分析

    对于时间复杂度: 本算法的核心部分为求最少比较次数和最多比较次数的两个while循环,while循环的结束取决于优先队列的长度。根据给进队的循环,则本算法的时间复杂度为O(n)。

    对于空间复杂度:由于用到了两个优先队列来分别表示大顶堆和小顶堆(用到了tempQueue和largeQueue),所以空间复杂度为T(2n) = O(n)。

    五、心得体会

    通过本次实践,我感触最深的就是其实贪心算法的策略我们一直都在使用,贪心算法并不是什么特别难懂的算法,只需要我们找到贪心选择的策略,再证明它是否可行,证明可行后使用贪心算法能够有效又高效率地解决问题。这次结对编程效果没有上一次的好,原因在于我们的思维限制在了一点上,其实可以两个人分别想出不同的贪心策略再检验是否可行,当一种策略不可行就马上换下一种。还有不足就是我还不能很好地证明自己的贪心选择策略,不能有理有据地向大家展示出来,导致自己做题的时候觉得可行,但其实自己也不太清楚其原因。

    
    
  • 相关阅读:
    滚轮事件的防冒泡、阻止默认行为的代码(效果是:只让当前div滚动,连当前文档都不滚动的效果)
    js原生封装getClassName()方法-ie不支持getElementsByClassName,所以要自己实现获取类名为className的所有元素
    使用 RequireJS 优化 Web 应用前端
    javascript的页面加载及性能优化(兼容IE7)
    JavaScript中的this陷阱的最全收集--没有之一
    mysql 配置MHA
    mysql 在linux下的完整安装过程
    vue 开发系列(八) 动态表单开发
    vue 开发系列(七) 路由配置
    vue 开发系列(六) 企业微信整合
  • 原文地址:https://www.cnblogs.com/besthunterhj/p/11882129.html
Copyright © 2011-2022 走看看