码迷,mamicode.com
首页 > 编程语言 > 详细

算法第四章上机实践报告

时间:2019-11-18 18:49:54      阅读:87      评论:0      收藏:0      [点我收藏+]

标签:致自己   clu   证明   cin   比较   优先   lse   题目   empty   

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

一、实践题目

给定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)。

 

五、心得体会

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


算法第四章上机实践报告

标签:致自己   clu   证明   cin   比较   优先   lse   题目   empty   

原文地址:https://www.cnblogs.com/besthunterhj/p/11882129.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!