您的位置:首页 > 其它

步步为营(二) 贪心(1)理论初探

2015-07-18 09:04 260 查看
等待了一年时间,这个系列的坑终于又开始填了……

不说废话,直接开始正题。

1.何为贪心?

贪心算法实际上指的是把问题划分成一个一个的子问题,然后针对当前的子问题,求出局部最优解,然后将子问题的最优解合并,最终获得总问题的最优解。

值得注意的是,在对问题求解时,贪心算法总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它做出的仅是在某种意义上的局部最优解

P.S:贪心子问题是独立的,有区别于动态规划(这个以后讨论动规的时候再聊)。

2.如何判断贪心

从上面这段话中不难看出,一个问题能够通过贪心来获取最优解的前提是:

问题可以被划分成多个子问题。

证明可以通过子问题的最优解可以获得最终的最优解。

子问题必须具有无后效性,也就是说,当前问题的求解的过程并不会影响之前的子问题的结果。

3.贪心策略的制定

1.制定最优解策略,从最初状态开始。

2.循环解决子问题,逐步缩小问题规模。针对每一个子问题,都运用局部最优解策略获取结果。

3.对每个子问题的解进行处理,获得最终结果

贪心问题的求解代码都不会很长,但是对于贪心策略的制定确是要费点心力。

一般来说,多用点数据验证验证,就能减少很多不必要麻烦。

下面让我们来看一个问题:

问题来源:NYOJ 71

独木舟上的旅行
时间限制:3000 ms  |  内存限制:65535 KB
难度:2


描述

进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别。一条独木舟最多只能乘坐两个人,且乘客的总重量不能超过独木舟的最大承载量。我们要尽量减少这次活动中的花销,所以要找出可以安置所有旅客的最少的独木舟条数。现在请写一个程序,读入独木舟的最大承载量、旅客数目和每位旅客的重量。根据给出的规则,计算要安置所有旅客必须的最少的独木舟条数,并输出结果。

输入

第一行输入s,表示测试数据的组数;

每组数据的第一行包括两个整数w,n,80<=w<=200,1<=n<=300,w为一条独木舟的最大承载量,n为人数;

接下来的一组数据为每个人的重量(不能大于船的承载量);

输出

每组人数所需要的最少独木舟的条数。

样例输入

3

85 6

5 84 85 80 84 83

90 3

90 45 60

100 5

50 50 90 40 60

样例输出

5

3

3

下面让我们来分析一下这道题。

n个人,每个人体重为arr[i]。船每次最多乘坐两个人,而且每次承载的体重不能超过w。求最少的渡船数。

换种描述方式,也就是说,有n个数,要求划分为多个集合,每个集合最多有两个元素,且两个元素的和不能超过w。求最小的集合数。

那么这道题能不能用贪心来做呢?

首先我们来看

1.问题可以被划分成多个子问题。

显而易见,这是可以的。整个数列的划分过程可以分解为两个数的组合过程

2.证明可以通过子问题的最优解可以获得最终的最优解。

这个也是可以理解的,对于这道题而言,两个数的组合过程的最优解就是两个数成功的组合成一个集合。而对于整个问题来说,最优解是尽可能多的让两个数进行组合。所以,通过子问题的最优解就可以得到整个问题的最优解。

3.子问题必须具有无后效性,也就是说,当前问题的求解的过程并不会影响之前的子问题的结果。

这个是显而易见的,当前进行组合的数都是未进行组合过的,所以肯定不会对之前的问题解造成任何影响。

现在我们知道了,这道题是可以通过贪心来做的,接下来的问题就是如何制定贪心策略。

从题意中得出,组合的过程有三种情况:

1. a+b <= w,那么判断a,b是不是最接近木船重量的,如果是可以划分到一个组里,从数组中删除a和b。如果不是,继续找。也就是说,优先进行a+b最大的进行组合

2. a+b >= w,那么a,b不可以放进一个组里,两个数与其他数重新进行组合。

3. 如果没有能与a进行组合的数字,则a自己成为一个集合,从数组中删除a;

那么我们可以用伪代码描述整个过程

for(从数组中遍历a)
{
for(从数组中遍历b)
{
if(a+b<=w)
{
if(a+b最接近w)
{
标记b;
}
}
else{
什么也不做,继续下一个对比;
}
}

if(b有标记)
{
从数组中删除a和b;
总集合数+1;
}
if(没有能与a组合的数)
{
从数组中删除a;
总集合数+1;
}
}


这样的话,直到两个循环执行完毕,那么总集合数就是最终的结果。

详细代码如下:

/*
************************************
Title: NYOJ71--独木舟上的旅行
************************************
Date:2015/07/18
************************************
author:刘旭
************************************
Memory:232KB
Time:8ms
************************************
*/
#include <stdio.h>

#define MAX 305

int main()
{
int T = 0;
scanf("%d", &T);    ///获取测试数据组数

while(T--)
{
int weight_people[MAX]; ///记录每个人的体重的数组
int vis[MAX];   ///记录每个人是否被删除的数据,vis[i] = 0表示这个人已经被运走,不能进行组合
int weight = 0; ///船的最大载重数
int num_people = 0; ///人的数量
scanf("%d%d", &weight, &num_people);

for(int i = 0; i < num_people; i++)
{
scanf("%d", &weight_people[i]); ///循环输入每个人的体重
vis[i] = 1; ///标记每个人
}

int ans = 0;    ///总集合数

for(int i = 0; i < num_people; i++)  ///循环遍历
{
if(0 == vis[i])     ///如果这个人被运走,计算下一个人
{
continue;
}

int key = -1;   ///判断是否有人组合
int max = -2;   ///目前组合的体重

for(int j = 0; j < num_people; j++)
{
if(0 == vis[j] || i == j)     ///如果这个人被运走或者和进行比对的人重复,计算下一个人
{
continue;
}

if(weight_people[i] + weight_people[j] <= weight) ///如果装的下两个人
{
if(weight_people[i] + weight_people[j] > max)   ///这两个人的体重最大
{
key = j;
max = weight_people[i] + weight_people[j];
}
}
}
if(-1 != key) ///如果装的下两个人
{
vis[i] = vis[key] = 0;    ///标记这两个人
ans ++; ///总集合数+1;
}
if(-1 == key)   ///没有能与a组合的人,独自上船
{
vis[i] = 0;
ans++;
}
}

printf("%d\n", ans);
}
}


俗话说,生命不息,折腾不止。这道简单的题时间居然在8ms~

其实症结很简单,两个for循环闹得,时间复杂度O(n^2)。那么有没有什么方法可以简化呢,当然可以。

这道题最关键的地方在于优先选择a+b最大的组合,那么我们就从这个方面入手。

针对一个有序数列a
,让里面元素按从小到大的顺序排列,则可知 a[1] <=a[2]<= a[3]…a[n-1]<=a
.

则很容易推导

1. a
+a[1] <= a
+a[x] (1< x < n)

2. a[1]+a
>= a[1]+a[x] (1< x < n)

也就是说,

1. 对于a
来说,如果a[1]+a
都不能小于w,那么他就不能与任何数相加小于w,只能一个数组成集合。

2. 对于a[1]来说,如果a
不行,那就查看a[n-1]是否可以,如果这样能找到一个数a[x],那么a[1]+a[x]一定是最接近w的值

所以我们可以写代码了

/*
**********************************
Title: NYOJ71--独木舟上的旅行
**********************************
Date:2015/07/18
**********************************
author:刘旭
**********************************
Memory:232KB
Time:0ms
**/
#include <cstdio>
#include <algorithm>

using namespace std;

#define MAX 305

int main()
{
int T = 0;
scanf("%d", &T);

while(T--)
{
int weight_people[MAX];
int vis[MAX];
int weight = 0;
int num_people = 0;
scanf("%d%d", &weight, &num_people);

for(int i = 0; i < num_people; i++)
{
scanf("%d", &weight_people[i]);
}

sort(weight_people, weight_people+num_people);

int ans = 0;
int pos_start = 0;
int pos_end = num_people-1;
while(pos_start <= pos_end)
{
if(weight_people[pos_start] + weight_people[pos_end] <= weight)
{
pos_end -= 1;
pos_start += 1;
ans += 1;
}
else
{
pos_end -= 1;
ans += 1;
}
}

printf("%d\n", ans);
}
}


时间复杂度O(nlogn),是不是很棒~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  acm 算法 贪心算法