算法——贪心算法


前言

贪心算法是我个人比较喜欢的算法,简单有效,思路往往很容易想到,但是要证明其正确性比较麻烦。


一、思想

贪心的意思在于在作出选择时,每次都要选择对自身最为有利的结果,保证自身利益的最大化。贪心算法就是利用这种贪心思想而得出一种算法。对于一个较大的问题,通过一种贪心策略,把复杂的问题划分为多个小问题,并且找到每个子问题的最优解,以此来获得整个问题的最优解。也就是说贪心算法是一种在每一步选择中都采取在当前状态下最好的选择,从而希望得到结果也是是最好的算法。但是贪心算法并不是在所有问题上都能得到整体最优解,只是可以在相当广泛的问题上得到整体最优解或者整体最优解的近似。

二、解题步骤

  1. 制定一个贪心策略,想好如何划分子问题以及如何求解子问题(不同的贪心策略往往可以得到不同的答案,但通常只有一个策略对应着正确的答案)
  2. 划分子问题
  3. 求解子问题的最优解
  4. 将所有子问题的解合并成整体的解

三、例题

1.圣诞老人的礼物

描述
圣诞节来临了,在城市A中圣诞老人准备分发糖果,现在有多箱不同的糖果,每箱糖果有自己的价值和重量,每箱糖果都可以拆分成任意散装组合带走。圣诞老人的驯鹿最多只能承受一定重量的糖果,请问圣诞老人最多能带走多大价值的糖果。

输入
第一行由两个部分组成,分别为糖果箱数正整数n(1 <= n <= 100),驯鹿能承受的最大重量正整数w(0 < w < 10000),两个数用空格隔开。其余n行每行对应一箱糖果,由两部分组成,分别为一箱糖果的价值正整数v和重量正整数w,中间用空格隔开。

输出
输出圣诞老人能带走的糖果的最大总价值,保留1位小数。输出为一行,以换行符结束。

样例输入
4 15
100 4
412 8
266 7
591 2

样例输出
1193.0

解题思路
计算每箱糖果的价值/重量比,并且每次都先选最大的(贪心策略),对价值重量比大的糖果尽可能多装,装完了再退而求其次装第二大的,以此类推。

示例代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const double eps = 1e-6;		//足够小,可以看作是0
struct Candy
{
	int v;			//价值
	int w;		//重量
	bool operator < (const Candy& c) const
	{//运算符重载,将用于下面的sort函数中
		return double(v) / w - double(c.v) / c.w > eps;
	}
}candies[110];		//题目给的范围是100,但通常多写10个用来防止数组溢出

int main()
{
	int n, w;	//糖果的箱数以及驯鹿能承受的总重量
	cin >> n >> w;
	for (int i = 0; i < n; i++)
	{//循环输入每箱糖果的价值和重量
		cin >> candies[i].v >> candies[i].w;
	}
	sort(candies, candies + n);	//依照价值重量比对每箱糖果进行排序
	double totalV = 0;	//挡墙已经装上的总价值,因为最后一次拿的时候不一定可以拿到整箱,所以用小数形式
	int totalW = 0;		//当前已经装上的总重量
	for (int i = 0; i < n; i++)
	{//i=0时对应从价值重量比最大的开始拿
		if (totalW + candies[i].w <= w)
		{//可以整箱拿走
			totalW += candies[i].w;
			totalV += candies[i].v;
		}
		else
		{//不能整箱拿走,也是最后一次拿糖果
			totalV += candies[i].v * double(w - totalW) / candies[i].w;
			break;
		}
	}
	printf("%.1f", totalV);
	return 0;
}

注意
若本题糖果只能整箱拿,则贪心法错误

2.电影节

描述
官方举办电影节,给定每部电影的放映时间区间,区间重叠的电影不可能同时看(端点可以重合),问最多可以看多少部电影。

输入
多组数据。每组数据开头是n(n<=100),表示共n场电影。
接下来n行,每行两个整数(0到1000之间),表示一场电影的放映区间
n=0则数据结束

输出
对每组数据输出最多能看几部电影

样例输入
8
3 4
0 7
3 8
15 19
15 20
10 15
8 18
6 12
0

样例输出
3

解题思路
每次都看结束时间最早的电影(贪心策略),于是将所有电影按照结束时间从小到大排序并且挑选即可。

示例代码

#include<iostream>
#include<algorithm>
using namespace std;

struct Movie
{
	int start_time;
	int end_time;
	bool operator < (const Movie& m) const
	{
		return end_time < m.end_time;
	}
}movies[110];

int main()
{
	int n;	//表示共n场电影
	while (cin >> n && n != 0)
	{//循环输入多组数据,输入n为0时停止
		for (int i = 0; i < n; i++)
		{
			cin >> movies[i].start_time >> movies[i].end_time;
		}
		sort(movies, movies + n);	//依照结束时间对所有电影排序
		int nowN = 0;	//当前已经看过的电影数目
		int nowEnd = 0; //当前电影结束时间
		for (int i = 0; i < n; i++)
		{
			if (movies[i].start_time >= nowEnd)
			{
				nowEnd = movies[i].end_time;
				nowN++;
			}
		}
		cout << nowN << endl;
	}
}

注意
按照结束时间和按照开始时间排序是两种不同的贪心策略,也是两种最容易想到的贪心策略,它们的区别就在于正确性,我们可以通过证明来证明第一种贪心策略是正确的,也可以通过证明来证明第二种贪心策略是不正确的.

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页