计硕3班-郭兴君-2022222279-第四章作业

全部都AC 2022-12-30 18:51:19

1、内容概况与学习心得

1.1 贪心算法设计思想

使用动态规划法将待求解问题分解成若干个相互重叠的子问题,每个子问题对应问题求解过程中的一个阶段,在各阶段子问题求解中,计算子问题的解并填入表中,当下一求解阶段需要再次计算此子问题时,可以通过查表获得该子问题的解而不用再次求解,从而避免了大量重复计算。贪心算法常用于优化问题近似解的求解。在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

1.2 贪心算法的设计要点

(1) 贪心算法在解决问题的的时候只考虑目前的最优情况,只根据当前已有的求解信息做出局部最优选择,而且一旦做出选择,不管将来有什么结果,这个选择都不会改变。

(2)贪心法每次所作出的选择只是在某种意义上的局部最优选择,这种局部最优选择并不是总能保证获得问题的整体最优解,但通常能获得近似最优解。

(3)在众多的计算机算法中,贪心策略是最能接近人们日常思维的一种解题策略。

1.3 最优子结构性质

一个问题的最优解包含其子问题的最优解时称此问题具有最优子结构性质,也称此问题满足最优性原理。最优子结构性质是某问题可用动态规划算法或食心算法求解的关键特征.

1.4 贪心选择策略

贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择 (盒心选择) 得到贪心算法通常以自顶向下的方式作出相继的贪心选择,每作一次盒心选择就将所求问题简化为规模更小的子问题,然后再求解一次贪心选择后产生的子问题解。

1.5 贪心算法的应用

  1. 哈弗曼编码
  2. 单元最短路径
  3. 最小生成树
  4. 背包问题
  5. 最优装载问题

1.6 心得体会

关于贪心算法的学习已经两周了,我也收获了不少,但还是对贪心算法的思想不够清晰,还是有所模糊。众所周知,贪心算法在几个基本算法是相对简单的算法了,思路也简单,每一步都能做出当前最好的选择。对于贪心算法,最重要的就是找到每次的局部最优解,而动态规划的关键在于找到状态转移方程。贪心算法又称为贪婪法,是用来寻找最优解问题的常用方法。与动态规划不同的是,贪心算法在求解问题时,总是选择对于当前子问题最好的选择。也就是贪心算法的本质是每次只顾眼前利益,并且到最后能获得最大利益。
我对贪心算法的学习一直在路上,过程也付出了努力,有时不是很懂贪心算法的思想时,加上过程也很艰难,自己也想过放弃,但是老师鼓舞人心的话语让我打消了这个念头,再次对自己充满毅力,坚信自己付出了时间和努力,一定会走到最后。在老师布置贪心算法的作业时,我开始很茫然,不停地看着老师的PPT例题讲解,翻看资料书一些例题理解它的思想,也搜过好些代码,慢慢总结规律,自己总算琢磨出贪心算法的思想以及它的思路,对它的限制和不足也有所了解,对于老师布置的作业,自己也总算A掉了几个题。学习贪心算法的过程,几乎都是在琢磨路上,不断翻看资料,借阅优秀的代码,到最后总算熟悉掌握了它的思路。

2. 以{0-1}背包问题和背包问题为例,讨论动态规划算法与贪心算法的异同

 

贪心算法与动态规划算法的差异 :

贪心算法和动态规划算法都要求问题具有最优子 结构性质,这是算法的一个共同点。

动态规划法通常以递推方式求解各个子问题,某个子问题的解依赖于已知子问题的最优解再作出选择。

贪心算法通常以一系列的局部贪心选择和求解, 每个局部阶段,先作贪心选择,再解子问题

3. 算法实验2:完成教材114页算法实现题4-2总结实验出现问题及解决方法。

3.1 问题描述

3.2 思路分析

贪心算法能够做到局部最优解,在本题中:

(1)如果你想要的是最多比较次数的合并方案,那么就要每一次合并的时候都是该序列中最长的那两个。

(2)如果你想要的是最少比较次数的合并方案,那么就要每一次合并的时候都是该序列中最短的那两个。

所采用的2 路合并算法合并2 个长度分别为m和n的序列需要m + n -1次比较(题目已给出),把这个序列合并在一起之后在接着计算,即可得出最优值。所以,一定要先对序列进行排序。
例:4 5 12 11 2 
排序2,4,5,11,12 
贪心策略:每次选最小的序列合并得到最少比较次数;每次选最大的序列合并得到最多比较次数。 
2 个长度分别为m和n的序列需要m + n -1次比较 
最多比较次数=(12+11-1)+ (12+11+5-1 )+ (12+11+5 +4 -1)+ (12+11+5+4+2-1 ) 
最少比较次数=2+4-1+5+6-1+11+11-1((((2+4-1)+5-1)+11-1)+12-1)

3.3 代码

#include<bits/stdc++.h>
using namespace std;

bool cmp(int a,int b) {
	return a>b;
}

int MaxMerge(int a[],int k) {
	int max=0;
	for(int i=0; i<k-1; i++) {
		sort(a+i,a+k,cmp);//降序排列 只对后面还没有操作的元素进行重新排序
		a[i+1]=a[i]+a[i+1];
		max=max+a[i+1]-1;
	}
	return max;
}

int MinMerge(int b[],int k) {
	int min=0;
	for(int i=0; i<k-1; i++) {
		sort(b+i,b+k);//升序排列
		b[i+1]=b[i]+b[i+1];
		min=min+b[i+1]-1;
	}
	return min;
}

int main() {
	int k;
	cin>>k;
	int a[k],b[k];
	int x;
	for(int i=0; i<k; i++) {
		cin>>x;
		a[i]=x;
		b[i]=x;
	}
	cout<<MaxMerge(a,k)<<" "<<MinMerge(b,k)<<endl;
	return 0;
}


 

 3.4 问题及解决方法

由题目已知得合并长度为m和n需要比较次数为m+n-1,要想得到最优合并顺序,根据贪心算法的思想,局部最优即全局最优,先考虑长度最短的两个数进行合并,合并后的数字加入数组中然后再次挑选长度最短的两个数合并,每次合并都让记录次数的值进行加法就可以,一直到将所有数据合并完为止。首先用一个数组存储要合并的序列,如果要求最优合并,则升序排列,每次合并后的数字替代合并前的第二个数字,然后对后面的再次排序,重复前面的步骤。如果是最差情况,则降序排列,从最大的开始,每次挑选最大的两个数字合并,合并后的数字加入原数组,再排序然后合并。
 

4. 算法实验2:完成教材118页算法实现题4-14总结实验出现问题及解决方法。

4.1 问题描述

4.2 问题分析

求最大费用时只需将石堆排列后从大到小,两两进行合并即可
求最小费用时,将石堆排列后,要尽可能的合并最少的次数且每次合并的石堆数为K堆
在求最小费用时,有时会出现特例,即每次合并K堆,最后一次合并时无法以K堆进行合并,
这样的话合并的结果就不是最小费用了,我们要将最小的堆合并最多次这样结果才会最小,
所以就要先判断原总堆数是否能使每次合并的堆数都为K堆,
如果不能的话就要在原堆数前面加上 X 个个数为0的堆来补齐缺少的堆数
例如共7堆最大合并5堆
石堆数 45 13 12 5 9 22 16
这时排序后为5 9 12 13 16 22 45
如果先合并前5堆 这样结果就为177
如果补零的话 0 0 5 9 12 13 16 22 45,每次合并K堆,结果为148

例题,排序5, 9, 12, 13, 16, 22, 45
最小:
判断是否需要添加0,不需要,进入加和,5 + 9 + 12 = 26;sum1 = 26,26进队;
13, 16, 22,26, 45;13 + 16 + 22 = 51;sum1 = 26 + 51 = 77,51进队;
26, 45, 51,26 + 45 + 51 = 122;sum1 = 77 + 122 = 199,结束。
最大:
45, 22, 16, 13, 12, 9, 5 ;
45 + 22 = 67;sum = 67,sum2 = 67,67进队;
67 + 16 = 83;sum = 67 + 16 = 83;sum2 = 83 + 67 = 150;150进队;
83 + 13 = 96;sum = 96 + 150 = 246......

4.3 代码

#include <iostream>
#include <queue>///队列库函数
using namespace std;
long long int max_sum;///合并成一堆的最大总费用
long long int min_sum;///合并成一堆的最小总费用
///优先队列会自动将队列中元素排序
priority_queue <int, vector<int>, less<int> > q1;///大数值优先队列,从大到小
priority_queue <int, vector<int>, greater<int> > q2;///小数值优先队列,从小到大
void get_maxsum();
void get_minsum(int);
int main()
{
    int n, k, x;///有n堆石子,每次至少选2 堆最多选k堆石子合并,x为临时变量
    cin >> n >> k;
    for(int i = 0; i < n; i++)
    {
        cin >> x;
        q1.push(x);
        q2.push(x);
    }
    max_sum = min_sum = 0;
    get_maxsum();
//    cout << "----------" << endl;
    get_minsum(k);
//    cout << "----------" << endl;
    cout << max_sum << " " << min_sum << endl;
    return 0;
}
void get_maxsum()
{///要想计算将n堆石子合并成一堆的最大总费用,
    ///就需要使相加次数最多并且先令大数相加
    long long sum = 0;///临时变量,用于计算每组石子相加总数
//    cout << "q1.size = " << q1.size() << endl;
    while((int)q1.size() >= 2)
    {///如果大数值优先队列中元素个数大于2,则进行相加操作
        sum = 0;
        for(int i = 0; i < 2; i++)
        {///令相加次数最多,则每次令2堆石子相加(规定每次至少选2堆)
            sum += q1.top();///将数字相加
            q1.pop();///将队列首位数字加上后,将其从队列中移除
        }
        q1.push(sum);///每次计算完一组石子相加,将该组石子组成的新的一堆石子放入队列
        max_sum += sum;///计算完一组后将该组费用放入总费用中
//        cout << "q1.size = " << q1.size() << endl;
    }
}
void get_minsum(int k)///规定每次最多选k堆石子合并成新的一堆
{///要想计算将n堆石子合并成一堆的最小总费用,
    ///就需要使相加次数最少并且先令小数相加
    ///还需要保证每次都以k个石堆相加,如果石子堆数不够,则补0
    ///如果不补,则最后一次相加的石子堆数不满k,则前面的值不是最小值
    long long sum = 0;
    while(q2.size() % (k-1) != 1)
    {///如果最小值优先队列中元素个数不能被k-1取余后余1,则在队列中添加0
    ///每组石子相加后回放回队列,所以每次是 k-1个石堆 + 之前一个石堆
        q2.push(0);
    }
//    cout << "q2.size = " << q2.size() << endl;
    while((int)q2.size() >= k)
    {
        sum = 0;
        for(int i = 0; i < k; i++)
        {
            sum += q2.top();
            q2.pop();
        }
        q2.push(sum);///每次计算完一组石子相加,将该组石子组成的新的一堆石子放入队列
        min_sum += sum;///计算完一组后将该组费用放入总费用中
//        cout << "q2.size = " << q2.size() << endl;
    }
}

 4.4 问题及解决方案

如上代码解析所示。

 

 

...全文
201 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

144

社区成员

发帖
与我相关
我的任务
社区描述
高校教学社区
软件工程 高校
社区管理员
  • dainwnu
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧