贪心算法之贪心的加勒比海盗

14天阅读挑战赛
努力是为了不平庸~
算法学习有些时候是枯燥的,这一次,让我们先人一步,趣学算法!欢迎记录下你的那些努力时刻(算法学习知识点/算法题解/遇到的算法bug/等等),在分享的同时加深对于算法的理解,同时吸收他人的奇思妙想,一起见证技术er的成长~

课前小故事

目录

课前小故事

一、什么是贪心算法?

1.5算法实现

二、实例分析

二、问题分析

3.1、数据结构定义

3.2、按重量排序

3.3、按照贪心策略找最优解

四、代码实战

4.3、欲知后事如何?请看下节解

从前,有一个很穷的人救了一条蛇的命,蛇为了报答他的救命之恩,于是就让这个人提
出要求,满足他的愿望。这个人一开始只要求简单的衣食,蛇都满足了他的愿望,后来慢慢
的贪欲生起,要求做官,蛇也满足了他。这个人直到做了宰相还不满足,还要求做皇帝。蛇
此时终于明白了,人的贪心是永无止境的,于是一口就把这个人吞掉了。所以,蛇吞掉的是宰相,而不是大象。故此,留下了“人心不足蛇吞相”的典故。

一、什么是贪心算法?

贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。贪心算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪心算法采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择,就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解。虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪心算法不要回溯

1.1贪心本质
一个贪心算法总是做出当前最好的选择,也就是说,它期望通过局部最优选择 一个贪心算法总是做出当前最好的选择,也就是说,它期望通过局部最优选择从而得到全局最优的解决方案。 从而得到全局最优的解决方案.
                                                                                                   《算法导论》 “算法导论”

1.2、贪心算法的基本思路

  • 建立数学模型来描述问题
  • 把求解的问题分成若干个子问题
  • 对每个子问题求解,得到子问题的局部最优解
  • 把子问题的解局部最优解合成原来问题的一个解

1.3、该算法存在的问题

  • 不能保证求得的最后解是最佳的
  • 不能用来求最大值或最小值的问题
  • 只能求满足某些约束条件的可行解的范围

1.4贪婪法的基本步骤:

步骤1:从某个初始解出发;
步骤2:采用迭代的过程,当可以向目标前进一步时,就根据局部最优策略,得到一部分解,缩小问题规模;
步骤3:将所有解综合起来。

1.5算法实现

  1. 从问题的某个初始解出发。
  2. 采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个部分解,缩小问题的范围或规模。
  3. 将所有部分解综合起来,得到问题的最终解。

二、实例分析

2.1问题描述
在北美洲南部,有一篇神秘的海域,那里碧海蓝天、阳光明媚。这正是传说中海盗最活跃的加勒比海。17世纪时,这里更是欧洲大陆的商旅舰队到达美洲的必经之地,所以当时的海盗活动非常猖獗,海盗不仅攻击过往商人,甚至攻击英国皇家舰…
有一天,海盗们截获了一艘装满各种各样古董的货船,每一件古董都价值连城,一旦打碎就失去了它的价值,虽然海盗船足够大,但载重量为C,没见古董的重量为wi,海盗们该如何把尽可能多数量的宝贝装上海盗船呢?

三、问题分析

根据问题描述可知这是一个可以用贪心算法求解的最优装载问题,要求装载的物品的数量尽可能多,而船的容量是固定的,那么优先把重量小的物品放进去,在容量固定的情况下,装的物品最多。采用重量最轻者先装的贪心选择策略,从局部最优达到全局最优,从而产生最优装载问题的最优解。

3.1算法设计

(1)当载重量为定值C时,wi越小时,可装载的古董数量n越大。只要依次选择最小重量的古董,直到不能再装为止;
(2)把n个古董的重量从小到大(非递减)排序,然后根据贪心策略尽可能多地选出前i个古董,直到不能继续装为止,此时达到最优;

例如:

古董的重量清单


根据重量排完序的重量

 

按照贪心策略,每次选择重量最小的古董放入(mp代表古董的重量,ans 代表已装裁的古董个数。
i=0, 选择排序后的第1个,装入重量tmp=2, 不超过载重量30, ans=1。
i=l,选择排序后的第2个,装入重量tmp -2+3=5,不超过载重量30, ans =2。
i=2,选择排序后的第3个,装入重量tmp= -5+4-9,不超过载重量30,ans =3。
i=3, 选择排序后的第4个,装入重量tmp= -9+5=14, 不超过载重量30,ans =4。
i=4, 选择排序后的第5个,装入重量tmp-14+7=21, 不超过载重量30, ans =5。
i=5,选择排序后的第6个,装入重量tmp=21+10-31,超过载重量30,算法结束。
即放入古董的个数为ans=5个。

三、代码设计

3.2、数据结构定义

根据算法设计描述,我们用一维数组存储古董的重量:

double w[N];  //一维数组存储古董的重量

3.3、按重量排序

可以利用C++中的排序函数sort,对古董的重量进行从小到大(非递减)排序。要使用此函数需引入头文件:

#include <algorithm>

sort(begin, end)  //参数begin和end表示一个范围,分别为待排序数组的首地址和尾地址
//sort 函数默认为升序

在本例中只需要调用sort 函数对古董的重量进行从小到大排序:

sort(w, w+n);  //按古董重量升序排序

3.4、按照贪心策略找最优解

首先用变量ans记录已经装载的古董个数,tmp代表装载到船上的古董的重量,两个变量都初始化为0。然后按照重量从小到大排序,依次检查每个古董,tmp加上该古董的重量,如果小于等于载重量C,则令

ans++;否则,退出。

代码如下;

//tmp 代表装载到船上的古董的重量,ans 记录已经装载的古董个数
int tmp = 0, ans = 0;
for(int i=0; i<n; i++)
{
	tmp += w[i];
	if(tmp<=c)
		ans++;
	else
		break;
}

四、代码实战

例如:

#include <iostream>
#include <algorithm>
const int N = 1000005;
using namespace std;
double w[N];  //古董的重量数组
int main()
{
	double c;
	int n;
	cout<<"请输入载重量C及古董个数n:"<<endl;
	cin>>c>>n;
	cout<<"请输入每个古董的重量,用空格分开:"<<endl;
	for(int i=0; i<n; i++)
	{
		cin>>w[i];  //输入每个物品重量
	}
	sort(w,w+n);
	//tmp 代表装载到船上的古董的重量,ans 记录已经装载的古董个数
	double tmp = 0.0;
	int ans = 0;
	for(int i=0; i<n; i++)
	{
		tmp+=w[i];
		if(tmp<=c)
			ans++;
		else
			break;
	}
	cout<<"能装入的古董最大数量为ans=";
	cout<<ans<<endl;
	
	return 0;
}

4.1、算法解析及优化拓展
1.算法复杂度分析
(1)时间复杂度:首先需要按古董重量排序,调用sort函数,其平均时间复杂度为O(nlogn),
输入和贪心策略求解的两个for语句时间复杂度均为O(m),因此时间复杂度为0(n + nlog())。
(2)空间复杂度:程序中变量tmp,ans等占用了一些辅助空间,这些助空间都是常数阶的,因此空复杂度为O(1)。
4.2、优化拓展
(1)这一个问题为什么在没有装满的情况下,仍然是最优解?算法要求装入最多数量,
假如c为5, 4个物品重量分别为1、3、5、7.排序后,可以装入1和3,最多装入两个。
分析发现是最优的,如果装大的物品,最多装一个或 者装不下,所以选最小的先装才能装入
最多的数量,得到解是最优的。


4.3、欲知后事如何?请看下节解析

在伪代码详解的第3步“按照贪心策略找最优解”,如果把代码替换成下面代码,
有什么不同?
首先用变量ans记录已经装载的古董个数,初始化为m; tmp 代表装载到船上的古董的
重量,初始化为0。然后按照重量从小到大排序,依次检查每个古董,tmp 加上该古董的重
量,如果tmp大于等于载重量c,则判断是否正好等于载重量c,并令ans=i+1;否则ans=i,
退出。如果tmp小于载重量e,i++, 继续下一个循环。

猜你喜欢

转载自blog.csdn.net/weixin_57825040/article/details/127593357
今日推荐