贪心算法与动态规划

案例一:
买东西时需要找给收银员六元三角,有两元五角、一元、一角的硬币可供选择,且要求找给的硬币数最小。
分析:
既然需要找给的硬币数目最小,那么可以通过每次在不超过找零金额的情况下选择面值最大的硬币:
找零金额:6.3    选取硬币:2.5
找零金额:3.8    选取硬币:2.5
找零金额:1.3    选取硬币:1.0
找零金额:0.3    选取硬币:0.1
找零金额:0.2    选取硬币:0.1
找零金额:0.1    选取硬币:0.1
至此,通过这种方法用六枚硬币完成了找零,在这一过程中,每次的选择都是当前情况下的最优选择,对于这个案例来说,六枚硬币即是整体的最优解。
案例二:
买东西时需要找给收银员一元五角,有一元一角、五角、一角的硬币可供选择,且要求找给的硬币数最小。
依然按照案例一的思路:
找零金额:1.5    选取硬币:1.1
找零金额:0.4    选取硬币:0.1
找零金额:0.3    选取硬币:0.1
找零金额:0.2    选取硬币:0.1
找零金额:0.1    选取硬币:0.1
最终使用了五枚硬币。考虑选择三枚0.5的硬币,五枚硬币显然不是最优解,案例一的思路在这里得出了错误的结果。
此时可以采取另外一种思路:
设0.1、0.5、1.1为第1、2、3种硬币的面值,c[i,j]表示可以用第0、……i种硬币对金额为j的金钱进行找钱所需要的最少硬币数。(注意:这里的i指硬币种类数而非硬币枚数,i表示可用硬币种类数,j表示需要找回的零钱)
那么对于第i种硬币来讲有两种选择:
一种是不使用第i种硬币,那么c[i,j]=c[i-1,j],还有一种则是使用第i种硬币,那么c[i,j]=c[i,j-value[i]]+1,(这里的value[i]指第i种硬币的面值,而+1则意指使用了一枚第i种硬币)
对于每一个c[i,j]来讲,它的值无外乎于c[i-1,j]和c[i,j-value[i]]+1这两种情况,而问题要求使用的硬币数最少,即可总结表达式:c[i,j]=min{c[i-1,j],c[i,j-value[i]]+1}   (其称为特征转移方程)
对于任意的i,c[i,0]都为0,因为用i种硬币对金额0进行找钱,只需要0枚硬币,对于任意的j,c[0,j]意为用0种硬币对金额为j的金钱进行找零,其属于未定义的状态,应该被赋值为+∞(参考c[i,j]=min{c[i-1,j],c[i,j-value[i]]+1})
//其上两种情况称作边界条件
同时还应该考虑特殊情况,即value[i]>j,那么第i枚硬币面值大于金额j,则显然不能用第i种硬币找零。
伪代码如下:

C
 
 
复制代码
if(i==0) c[i][j]=max; 
if(j==0) c[i][j]=0;
}else{
  if(j>=value[i]){
    c[i][j]=min(c[i-1][j],1+c[i][j-value[i]])
  
  }else{
    T[i][j]=T[i-1][j];
  }
}

总结:
在案例一和案例中,总共采取了两种思想来解决最小硬币数找零的问题。
第一种思想称之为贪心算法
第二种思想则称之为动态规划
上方所讨论的问题是01背包的一种

01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。

在找零问题中,每种硬币都只有选与不选两种情况,可归于01背包问题中。
动态规划算法有自顶向下的备忘录法和自底向上两种,而找零问题中所采取的是自底向上的形式。

动态规划的实质是分治思想和解决冗余,动态规划算法是将问题分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。

例如在用递归计算斐波那契数列时

C
 
 
复制代码

int fib(int n)
{
  if(n<=0)
        return 0;
    if(n==1)
        return 1;
    return fib( n-1)+fib(n-2);
}

在输入数字6时

 

 

在这个递归中,每个子节点都会被执行一次,其中fb(2)被执行了5次,但我们可以将执行过的子节点保存起来,后面要用到的时候直接查表调用就可以节约大量的时间。

自顶向下的备忘录法
伪代码如下

C
 
 
复制代码
int fib(n,memo){
    if(memo[n]!=None)
      return memo[n];
    if(n==1||n==2)
        result=1;
    else
      result=fib(n-1)+fib(n-2);
    memo[n]=result;
    return result;
}

自底向上

C
 
 
复制代码
int fib(n,memo){
    n[0]=n[1]=1;
  for(int i=2;i<n;i++){
      n[i]=n[i-1]+n[i-2];
  }
}
//一道贪心算法题目
P5019 铺设道路
题目描述
春春是一名道路工程师,负责铺设一条长度为 nn 的道路。
铺设道路的主要工作是填平下陷的地表。整段道路可以看作是 nn 块首尾相连的区域,一开始,第 ii 块区域下陷的深度为 d_idi 。
春春每天可以选择一段连续区间[L,R][L,R] ,填充这段区间中的每块区域,让其下陷深度减少 11。在选择区间时,需要保证,区间内的每块区域在填充前下陷深度均不为 00 。
春春希望你能帮他设计一种方案,可以在最短的时间内将整段道路的下陷深度都变为 00 。
输入格式
输入文件包含两行,第一行包含一个整数 nn,表示道路的长度。 第二行包含 nn 个整数,相邻两数间用一个空格隔开,第ii 个整数为 d_idi 。
输出格式
输出文件仅包含一个整数,即最少需要多少天才能完成任务。

C
 
 
复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
  int n,sum=0;
  scanf("%d",&n);
  int a[n];
  for(int i=0;i<n;i++){
    scanf("%d",&a[i]);
    sum+=a[i];
  }
  int r,l,right,left,len=0,count=0;
  while(sum!=0){
    for(int i=0;i<n;i++){
      if(a[i]!=0) l=r=i;
            else continue;
      while(r+1!=n&&a[r+1]!=0) r++;
      if(r-l+1>len){
                len=r-l+1;
                left=l;
                right=r;
            }
            i=r;
    }
        if(len==0) break;
    for(int j=left;j<=right;j++){
            a[j]-=1;
        }
        sum-=len;
        count++;
        len=0;
  }
  printf("%d",count);
    system("pause");
}
//因为采用纯模拟法,最后三个数据点出现了超时问题
最后引用《算法图解》中相关内容的小结
❑贪婪算法寻找局部最优解,企图以这种方式获得全局最优解。
❑ 对于NP完全问题,还没有找到快速解决方案。
❑ 面临NP完全问题时,最佳的做法是使用近似算法。
❑ 贪婪算法易于实现、运行速度快,是不错的近似算法。
❑ 需要在给定约束条件下优化某种指标时,动态规划很有用。
❑ 问题可分解为离散子问题时,可使用动态规划来解决。
❑ 每种动态规划解决方案都涉及网格。
❑ 单元格中的值通常就是你要优化的值。
❑ 每个单元格都是一个子问题,因此你需要考虑如何将问题分解为子问题。
❑ 没有放之四海皆准的计算动态规划解决方案的公式。
 
 
 
 
posted @ 2021-06-26 19:33  LunasDial  阅读(442)  评论(0)    收藏  举报