贪心算法总结(转)

一、算法的基本情况说明:

     贪心算法的定义:在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心。

     从贪心算法的定义可以看出,贪心算法不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。

贪心算法存在问题: 
    1. 不能保证求得的最后解是最佳的; 
    2. 不能用来求最大或最小解问题; 
    3. 只能求满足某些约束条件的可行解。

 贪心算法适用的条件:
       问题的求解可以由一系列的决策步骤构成,每步决策依赖于某种局部最优的贪心策略。正确的贪心策略要保证每一步基于局部优化性质的选择最终导致全局的最优解。
如果不具有上述性质,贪心法对某些实例只能得到近似解。这就要求我们在求解贪心类的问题的时候我们将问题分成几个步骤,然后按步骤的进行,利用我们所设置的贪心原则,先求每一步的局部最优解最后求出全局最优解。

利用贪心策略解题,需要解决两个问题:
    (1)该题是否适合于用贪心策略求解;
    (2)如何选择贪心标准,以得到问题的最优/较优解。
     当我们选择好贪心标准后,我们是需要对这个贪心标准进行证明的,验证这个贪心标准是有效的。但是证明往往是比较困难的。这需要扎实的数学功底。
这里简单的给我证明贪心标准的两个方法:
    (1)数学归纳法
    (2)交换论证发
二、典型例题分类分析
       1.活动安排问题
        设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si<fi。如果选择了活动i,则它在半开时间区间[si ,fi )内占用资源。若区间[si ,fi )与区间[sj,fj )不相交,则称活动i与活动j是相容的。当 si ≥ fj 或 sj ≥ fi 时,活动i与活动j相容。活动安排问题就是在所给的活动集合中选出最大的相容活动子集合。
       首先我们分析,想要安排最多的活动,那就必须得让那些结束的早的活动先进行,这样才会有更多的时间来安排其他的活动,这样我们就确定了我们的贪心思。
数据结构
struct action{
int s; //起始时间
int f;         //结束时间
int index; //活动的编号
};
活动的集合E记为数组:
action a[1000];
按活动的结束时间升序排序
排序比较因子:
bool cmp(const action &a, const action &b)
{
if (a.f<=b.f) return true;
return false;
}
使用标准模板库函数排序(下标0未用):
sort(a, a+n+1, cmp);
//形参数组b用来记录被选中的活动
void GreedySelector(int n, action a[], bool b[])
{
  b[1] = true;     //第1个活动是必选的
  //记录最近一次加入到集合b中的活动
  int preEnd = 1;
  for(int i=2; i<=n; i++)
    if (a[i].s>=a[preEnd].f)
    {
      b[i] = true;
      preEnd = i;
    }
}

       2.背包问题

       给定一个载重量为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;
}
使用标准模板库函数排序(最好使用stable_sort()函数,在性价比相同时保持输入的顺序):
sort(a, a+n, cmp);

//形参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;
}

        3.钓鱼问题

问题描述: 
约翰有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);

        </div>
            </div>
展开阅读全文

没有更多推荐了,返回首页