贪心算法总结
一、算法思想
贪心法的基本思路:
从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止。
该算法存在问题:
1. 不能保证求得的最后解是最佳的;
2. 不能用来求最大或最小解问题;
3. 只能求满足某些约束条件的可行解的范围。
实现该算法的过程:
从问题的某一初始解出发;
while 能朝给定总目标前进一步;
求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
二、ACM做题情况
本专题,学习贪心算法,所给的18道题中只AC了14道(其中还有两题相同),除了几道水题以外,大部分题目是用到贪心算法来解决的,做了这套题,虽然感觉很困难,但想办法还是能AC几道题的,做过这些题目使我对贪心算法印象加深,没有以前感觉那么抽象,算是有进步吧,但还是不熟练,还需要加强训练。简短截说,还是在复习回顾一下,关于贪心算法的经典题目吧。
三、典型例题分类分析
【背包问题】
给定一个载重量为M的背包,考虑n个物品,其中第i个物品的重量 ,价值wi (1≤i≤n),要求把物品装满背包,且使背包内的物品价值最大。
有两类背包问题(根据物品是否可以分割),如果物品不可以分割,称为0—1背包问题(动态规划);如果物品可以分割,则称为背包问题(贪心算法)。
有3种方法来选取物品:
(1)当作0—1背包问题,用动态规划算法,获得最优值220;
(2)当作0—1背包问题,用贪心算法,按性价比从高到底顺序选取物品,获得最优值160。由于物品不可分割,剩下的空间白白浪费。
(3)当作背包问题,用贪心算法,按性价比从高到底的顺序选取物品,获得最优值240。由于物品可以分割,剩下的空间装入物品3的一部分,而获得了更好的性能。
struct bag{
int w; //物品的重量
int v; //物品的价值
double c; //性价比
}a[1001]; //存放物品的数组
排序因子(按性价比降序):
bool cmp(bag a, bag b){
return a.c >= b.c;
}
//形参n是物品的数量,c是背包的容量M,数组a是按物品的性价比降序排序
double knapsack(int n, bag a[], double c)
{
double cleft = c; //背包的剩余容量
int i = 0;
double b = 0; //获得的价值
//当背包还能完全装入物品i
while(i<n && a[i].w<cleft)
{
cleft -= a[i].w;
b += a[i].v;
i++;
}
//装满背包的剩余空间
if (i<n) b += 1.0*a[i].v*cleft/a[i].w;
return b;
}
如果要获得解向量,则需要在数据结构中加入物品编号:
struct bag{
int w;
int v;
double x; //装入背包的量,0≤x≤1
int index; //物品编号
double c;
}a[1001];
double knapsack(int n, bag a[], double c)
{
double cleft = c;
int i = 0;
double b = 0;
while(i<n && a[i].w<=cleft)
{
cleft -= a[i].w;
b += a[i].v;
//物品原先的序号是a[i].index,全部装入背包
a[a[i].index].x = 1.0;
i++;
}
if (i<n) {
a[a[i].index].x = 1.0*cleft/a[i].w;
b += a[a[i].index].x*a[i].v;
}
return b;
}
【找零钱问题】
问题描述:
当前有面值分别为2角5分,1角,5分,1分的硬币,请给出找n分钱的最佳方案(要求找出的硬币数目最少)
问题分析:
根据常识,我们到店里买东西找钱时,老板总是先给我们最大面值的,要是不够再找面值小一点的,直到找满为止。如果老板都给你找分数的或者几角的,那你肯定不干,另外,他也可能没有那么多零碎的钱给你找。其实这就是一个典型的贪心选择问题。
问题的算法设计与实现:
先举个例子,假如老板要找给我99分钱,他有上面的面值分别为25,10,5,1的硬币数,为了找给我最少的硬币数,那么他是不是该这样找呢,先看看该找多少个25分的, 99/25=3,好像是3个,要是4个的话,我们还得再给老板一个1分的,我不干,那么老板只能给我3个25分,由于还少给我24,所以还得给我2个10分的和4个1分。
具体实现伪代码
//找零钱算法
//By falcon
//输入:数组m,依次存放从大到小排列的面值数,n为需要找的钱数,单位全部为分
//输出:数组num,对照数组m中的面值存放不同面值的硬币的个数,即找钱方案
比如要找N分钱,先拿N除最大零钱面值,可以取模得出余数。
当然取整就是所找的最大面值零钱的个数。
所得余数再次处理,用的是一个循环结构。
N输入取值
M是定义的面值M[0]是最大面值
K是一个数组,存储各面值零钱的个数
i=0
do while (N>0)
K[0]=int(N/M[i])
N=mod(N,M[i])
i++
end do
【钓鱼问题】
问题描述:
约翰有h(1≤h≤16)个小时的时间,在该地区有n(2≤n≤25)个湖,这些湖刚好分布在一条路线上,该路线是单向的。约翰从湖1出发,他可以在任一个湖结束钓鱼。但他只能从一个湖到达另一个与之相邻的湖,而且不必每个湖都停留。
假设湖i(i=1~n—1),以5分钟为单位,从湖i到湖i+1需要的时间用ti(0<ti≤192)表示。例如t3=4,是指从湖3到湖4需要花20分钟时间。
已知在最初5分钟,湖i预计钓到鱼的数量为fi(fi≥0)。以后每隔5分钟,预计钓到鱼的数量将以常数di(di≥0)递减。如果某个时段预计钓到鱼的数量小于或等于di,那么在下一时段将钓不到鱼。为简单起见,假设没有其它的钓鱼者影响约翰的钓鱼数量。
编写程序,帮助约翰制定钓鱼旅行的计划,以便尽可能多的钓到鱼。
问题要求:
输入
对每组测试例,第一行是n,接下来一行是h。下面一行是n个整数fi(1≤i≤n),然后是一行n个整数di(1≤i≤n),最后一行是n—1个整数ti(1≤i≤n—1)。
输入
对每组测试例,第一行是n,接下来一行是h。下面一行是n个整数fi(1≤i≤n),然后是一行n个整数di(1≤i≤n),最后一行是n—1个整数ti(1≤i≤n—1)。
对每个测试例,输出在每个湖上花费的时间,这是约翰要实现钓到最多的鱼的计划(必须使整个计划在同一行输出)。接下来一行是钓到的鱼的数量。(如果存在很多方案,尽可能选择在湖1钓鱼所耗费的时间,即使有些时段没有钓到鱼;如果还是无法区分,那就尽可能选择在湖2钓鱼所耗费的时间,以此类推。)
问题分析:
1)数据结构
每个湖预计钓到鱼的数量,定义为数组:#define NUM 30
int f[NUM];
每个湖预计钓到鱼的数量的递减值,定义为数组:
int d[NUM];
相邻湖之间的旅行时间,定义为数组:
int t[NUM];
钓鱼计划,定义为数组:
int plan[NUM];
湖的个数n,用于钓鱼的时间h,尽可能多的钓鱼数量best。
2)枚举在任意一个湖结束钓鱼时的最优钓鱼计划
首先把用于钓鱼的时间h,由小时转换为以5分钟为单位的时间:h=h×60/5;
这样把钓5分钟鱼的时间称为钓一次鱼。由于约翰从湖1出发,可以在任一个湖结束钓鱼,要得到最优解,就需要进行搜索。
3)采用贪心策略,每次选择鱼最多的湖钓一次鱼
对于每个湖来说,由于在任何时候鱼的数目只和约翰在该湖里钓鱼的次数有关,和钓鱼的总次数无关,所以这个策略是最优的。一共可以钓鱼time次,每次在n个湖中选择鱼最多的一个湖钓鱼。
采用贪心算法构造约翰的钓鱼计划。
可以认为约翰能从一个湖“瞬间转移”到另一个湖,即在任意一个时刻都可以从湖1到湖pos中任选一个钓一次鱼。
过程实现代码:
//从湖1起到湖pos止,花费时间time(不含路程)的钓鱼计划
void greedy(int pos, int time)
{
if (time <= 0) return; //时间已经用完
int i, j;
int fish[MAXN];
int p[MAXN];
int t = 0;
for (i = 0; i < pos; ++i)
fish[i] = f[i];
memset(p, 0, sizeof(p));
……
}
//在时间time内,选择鱼最多的湖钓鱼;如果鱼都没有了,就把时间放在湖1上
for (i = 0; i < time; ++i)
{
int max = 0; //鱼最多的湖中,鱼的数量
int id = -1; //鱼最多的湖的编号
//查找鱼最多的湖中,鱼的数量和湖的编号
for (j = 0; j < pos; ++j)
if (fish[j] > max){
max = fish[j];
id = j;
}
if (id != -1) //找到了,进行钓鱼处理
{
++p[id];
fish[id] -= d[id];
t += max;
}
//没有找到(从湖1起到湖pos全部钓完了),就把时间放在湖1上
else ++p[0];
}
//处理最优方案
if (t > best)
{
best = t; //最优值
memset(plan, 0, sizeof(plan));
for (i = 0; i < pos; ++i) //最优解
plan[i] = p[i];
}
输出钓鱼计划时,再把5乘回去,就变成实际的钓鱼时间(分钟):
for (i=0; i<n-1; ++i)
printf("%d, ", plan[i] * 5);
printf("%d\n", plan[n-1] * 5);
printf("Number of fish expected: %d\n", best);
实现代码:
#include <iostream>
#include <queue>
using namespace std;
struct data {
int f,d,id;
}a[30],b;
int n,t,tran[30],x,ans,maxi,save[30],tmp[30],tt;
priority_queue <data> q;
bool operator<(data a,data b) {
if (a.f==b.f) return a.id>b.id;
else return a.f<b.f;
}
int main() {
while (~scanf("%d",&n)) {
if (n==0) break;
scanf("%d",&t);
t *= 12;
for (int i=1;i<=n;i++)
scanf("%d",&a[i].f);
for (int i=1;i<=n;i++) {
scanf("%d",&a[i].d);
a[i].id = i;
}
tran[1] = 0;
for (int i=2;i<=n;i++) {
scanf("%d",&x);
tran[i] = tran[i-1] + x;
}
ans = -1;
memset(save,0,sizeof(save));
for (int i=1;i<=n;i++) {
maxi = 0;
memset(tmp,0,sizeof(tmp));
tt = t - tran[i];
while (!q.empty()) q.pop();
for (int j=1;j<=i;j++)
q.push(a[j]);
while (tt>0) {
b = q.top();
q.pop();
tt--;
maxi += b.f;
tmp[b.id]++;
b.f -= b.d;
if (b.f<=0) b.f = 0;
q.push(b);
}
if (maxi>ans) {
ans = maxi;
for (int j=1;j<=i;j++)
save[j] = tmp[j];
}
}
for (int i=1;i<n;i++)
printf("%d, ",save[i]*5);
printf("%d\n",save[n]*5);
printf("Number of fish expected: %d\n\n",ans);
}
return 0;
}