标签:
转载地址:http://blog.csdn.net/liuxucoder
等待了一年时间,这个系列的坑终于又开始填了……
不说废话,直接开始正题。
贪心算法实际上指的是把问题划分成一个一个的子问题,然后针对当前的子问题,求出局部最优解,然后将子问题的最优解合并,最终获得总问题的最优解。
值得注意的是,在对问题求解时,贪心算法总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它做出的仅是在某种意义上的局部最优解。
P.S:贪心子问题是独立的,有区别于动态规划(这个以后讨论动规的时候再聊)。
从上面这段话中不难看出,一个问题能够通过贪心来获取最优解的前提是:
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); } }
其实症结很简单,两个for循环闹得,时间复杂度O(n^2)。那么有没有什么方法可以简化呢,当然可以。
这道题最关键的地方在于优先选择a+b最大的组合,那么我们就从这个方面入手。
针对一个有序数列a[n],让里面元素按从小到大的顺序排列,则可知 a[1] <=a[2]<= a[3]…a[n-1]<=a[n].
则很容易推导
1. a[n]+a[1] <= a[n]+a[x] (1< x < n)
2. a[1]+a[n] >= a[1]+a[x] (1< x < n)
也就是说,
1. 对于a[n]来说,如果a[1]+a[n]都不能小于w,那么他就不能与任何数相加小于w,只能一个数组成集合。
2. 对于a[1]来说,如果a[n]不行,那就查看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); } }
标签:
原文地址:http://blog.csdn.net/xia842655187/article/details/51944642