二周总结—贪心算法+浅谈动态规划

#贪心算法(一种特殊的动态规划,由于其具有贪心选择性质,保证了子问题只会被计算一次,不会被多次计算,因此贪心算法其实是最简单的动态规划。)
贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择(但最后解不一定是最好的)。也就是在某种意义上的局部最优解(并不是整体)。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。(自己的理解就是对一个大的问题转化为小的问题,解决那些小问题时,所使用的方法对应到解决问题的效率,而贪心算法就是解决局部问题的最优方法,虽然用贪心解决的局部最优到最后不一定会使最终结果最优,但这的确是我们平常解决问题的好方法。)
一思路

1.建立数学模型来描述题目(从所得模型中找出可以贪的地方);
2.把求解的问题分为若干子问题(从局部最优入手);
3.对每一子问题的最优求解(贪的实现);
4.把子问题的解局部最优解合成原来解问题的一个解(但不一定使最终结果最优);
二列题分析

1.有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
在这里插入图片描述
对这问题的分析:其实这问题很简单,在总量限制的情况下,想要尽可能的装的东西的总价值更多,那么只有把最贵重的东西先装,然后装下次贵重的,一次装下去直到背包被装满(这里就是贪的地方,先装贵的,那么装下来自然就装咯总佳值最高的东西)。(如果这些东西不可以分割的话,那么就不得只考虑最贵的,还得考虑重量,那么就依次从最贵又轻的开始装。)

#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
struct kd      //结构体来描述物品的信息,n为重量,v为价,x为性价比也是我们贪的比较;
{
    int n;
    int v;
    double x;
}a[101];           
bool cap(kd a,kd b)    //比较函数,为下面的排序函数定规则;
{
    if(a.x!=b.x) return a.x>b.x;
    else return a.n>b.n;
}
int main()
{
    int k,w,s,i,j;    //k为测试组数,w为背包空间,s为物品件数
    cin>>k;
    double b[k]={0};  //用来储存最后的价值总额;
    for(i=0;i<k;i++)
        {
            cin>>w>>s;
            for(j=0;j<s;j++)
                {
                    cin>>a[j].n>>a[j].v;
                    a[j].x=1.0*a[j].v/a[j].n;
                }
            sort(a,a+s,cap);    //对输入的物品进行性价比从高到低的排序;
            for(j=0;j<s;j++)
                {
                    if(a[j].n>w)
                        {
                            b[i]+=w*a[j].x;
                            break;
                        }
                    else
                        {
                            b[i]+=a[j].n*a[j].x;
                            w-=a[j].n;
                        }
                }
        }
    for(i=0;i<k;i++)
        printf("%.2f\n", b[i]);
    return 0;
}

小结:背包类的问题,贪的思想就是性价比高的。

2.均分纸牌:有N堆纸牌,编号分别为1,2,…,n。每堆上有若干张,但纸牌总数必为n的倍数.可以在任一堆上取若干张纸牌,然后移动。移牌的规则为:在编号为1上取的纸牌,只能移到编号为2的堆上;在编号为n的堆上取的纸牌,只能移到编号为n-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。例如:n=4,4堆纸牌分别为:① 9 ② 8 ③ 17 ④ 6 移动三次可以达到目的:从③取4张牌放到④ 再从③区3张放到②然后从②去1张放到①。
设a[i]为第I堆纸牌的张数(0<=I<=n),v为均分后每堆纸牌的张数,s为最小移动次数。

贪心算法分析:按照从左到右的顺序移动纸牌。如第I堆的纸牌数不等于平均值,则移动一次(即s加1),分两种情况移动:

1.若a[i]>v,则将a[i]-v张从第I堆移动到第I+1堆;

2.若a[i]<v,则将v-a[i]张从第I+1堆移动到第I堆。

为了设计的方便,我们把这两种情况统一看作是将a[i]-v从第I堆移动到第I+1堆,移动后有a[i]=v; a[I+1]=a[I+1]+a[i]-v。
注:如果后一堆比前一堆所差的还少,不能说明这是的贪心是错误的。

#include <iostream>
using namespace std;
int main()
{
    int n,i,a[101],p,s=0,b=0;    //n为堆数,a[i]存每堆的牌数,s为总牌数,p为每堆的平均牌数,b为所需步数;
    cin>>n;
    for(i=0;i<n;i++)
       {
           cin>>a[i];
           s+=a[i];
       }
    p=s/n;
    for(i=0;i<n;i++)
       {
          if(a[i]!=p)
            {
               a[i+1]+=a[i]-p; //有贪心思想的数学公式,也是每后一堆可以接受上一堆多的,也可以移给上一堆少的;
               a[i]=p;         //移后真是所保留的平均牌数;
               b++;
            }
       } 
    cout<<b<<endl;
    return 0;
}

小结:这题贪的思想就是挨着一堆一堆的移成平均数,这是所寻找的数学公式:a[I+1]=a[I+1]+a[i]-v。

训练题
1.金银岛[题目]:(http://sdau.openjudge.cn/tx/03/):与背包问题的解决方法一样,从性价比高的开始取。

2.[题目]:(http://sdau.openjudge.cn/tx/11/):N头奶牛,每头奶牛有自己的高度Hi,N头奶牛的总高度为S。书架高度为B(1 ≤ B ≤ S < 2,000,000,007)用最少的牛叠出比N高。
分析:这是一个十分简单的问题,需要最少的牛,那么只需依次将最高的牛取出直到达到所需的高度就行。

#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    int n,b,i,a[20000],d=0,s=0;
    cin>>n>>b;
    for(i=0;i<n;i++)
        cin>>a[i];
    sort(a,a+n);             //从小到大排序,只需最后从大到小取出;
    for(i=n-1;d<b;i--,s++)
        d+=a[i];
    cout<<s<<endl;
    return 0;
}

3.装箱问题:[题目]:(http://sdau.openjudge.cn/tx/05/)
贪心思路分析:从最大的开始装,剩余空间用小的进行填补。1.装箱的总面积一定;2.如果装66的一个箱子就装满;3.如果装一个55的就还可以装11个11的;4.如果装44的那么剩余空间就可用5个22的填满,或者用20个11的谈满,亦或是用22与11的组合;5.如果是33的话有装满需要4个,不足4个就需要用22的与11的进行填满;6.如果是22的与1*1的就可以直接计算。

#include <iostream>
#include <stdio.h>
#include <cmath>
using namespace std;
int p1,p2,p3,p4,p5,p6,s;  //定义7个全局变量好进行对不同组数据的验算;
int main()
{
    while(scanf("%d%d%d%d%d%d",&p1,&p2,&p3,&p4,&p5,&p6)&&p1+p2+p3+p4+p5+p6) //对数据经行输入,同时判断结束的条件输入000000时;
    {
        s=0;
        s+=p6+p5+p4;      //大箱子一次只能装一个,剩的空间用小的填补;
        p1-=min(p1,p5*11);  //取两者之间小的哪一个,也是装咯p5的剩下用1*1的经行填补;
        if(p2<p4*5)         //装咯4*4的剩下的填补情况;
            p1-=min(p1,(p4*5-p2)*4);
        p2-=min(p2,p4*5);
        s+=ceil(p3/4.0);    //装3*3的个数;
        p3%=4;
        if(p3==1)           //填补装咯3*3的剩余空间的填补;
            {
                if(p2<5)
                    p1-=min(p1,(5-p2)*4);
                p1-=min(p1,7);
                p2-=min(p2,5);
            }
        if(p3==2)
            {
                if(p2<3)
                    p1-=min(p1,(3-p2)*4);
                p1-=min(p1,6);
                p2-=min(p2,3);
            }
        if(p3==3)
            {
                if(p2>=0)
                    p1-=min(p1,4);
                p1-=min(p1,5);
                p2-=min(p2,1);
            }
        s+=ceil(p2/9.0);    //对余下的2*2进行装箱;
        p2%=9;
        if(p2)
            p1-=min(p1,(9-p2)*4);
        s+=ceil(p1/36.0);  //余下的1*1进行装箱;
        cout<<s<<endl;
    }
}

4.电池寿命:[题目]:(http://sdau.openjudge.cn/tx/12/)
贪心思路分析:对于每组数据只要判断最大的那个数是不是比其余的数的和都要大,
1)如果成立的话那当然就是剩下的所有电池与最大的电池车轮战,最大值为n-1个数的和,
2)如果不成立的话那么最大就是n个数的和的一半,也就是说电池电量是一定可以全部用完的。
尽可能的时电池寿命充分利用。

#include <iostream>
#include <cmath>
#include <stdio.h>
using namespace std;
int main()
{
     int n,x,maxn,s,s1;
     while(cin>>n)
     {
        s=0;
        maxn=0;
        for(int i=0;i<n;i++)
          {
              cin>>x;
              maxn=max(maxn,x);
              s+=x;
          }
        s1=s-maxn;
        if(s1<maxn)
            printf("%.1f\n", (double)s1);
        else printf("%.1f\n", s/2.0);
     }
     return 0;
}

总结:贪心思想完全可以用在生活中,比如平时作业与时间的安排上,完全可以利用贪心思想对选择对自己最有利,效率最高的的方法,如先交的先做。人在做任何事时都有选择如何去做,而贪心就是局部选择效果最好的做法。所以应该将贪心融入我平时做事中;

##动态规划:动态规划问题是面试题中的热门话题,如果要求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子问题,并且小问题之间也存在重叠的子问题,则考虑采用动态规划。
动态规划两个性质:(对比) 贪心算法:
1)重叠子问题 1)贪心选择性质
2)最优子结构 2)最优子结构
最优子结构性质;是指问题的最优解包含其子问题的最优解时,就称该问题具有最优子结构性质;
重叠子问题:指的是子问题可能被多次用到,多次计算,而动态规划就是为了消除其重叠子问题而设计的。
使用动态规划特征

  1. 求一个问题的最优解 ;
  2. 大问题可以分解为子问题,子问题还有重叠的更小的子问题 ;
  3. 整体问题最优解取决于子问题的最优解(状态转移方程) ;
  4. 从上往下分析问题,从下往上解决问题 ;
  5. 讨论底层的边界问题;
    动态规划需要注意的要点:
    1)状态(小规模问题的数学表示);
    2)状态转移方程(大规模问题如何转化为更小的问题);
    3)最小状态(最小规模的问题);
    4)要求的返回值是什么;
    1、动态规划的设计,其实就是利用最优子结构和重叠子问题性质对穷举法进行优化,通过将中间结果保存在数组中,实现用空间来换取时间交换,实现程序的快速运行。(动态规划求解时,一般都会转化为网格进行求解,而且因为是空间换时间(避免了子问题的重复计算),因此一般迭代求解)。2、一般对所要求解的问题的时间复杂度要求比较高的,可以优先考虑如下几种算法。①分治法②递归法③贪心算法④动态规划算法
    动态规划杀手锏:1)建模:最优子结构状态转移方程边界2)实现:递归法备忘录法(从上倒下,非全二叉树,hash保存!)自底而上(迭代实现)
发布了12 篇原创文章 · 获赞 0 · 访问量 140

猜你喜欢

转载自blog.csdn.net/weixin_46446866/article/details/104818025