贪心算法

一、基本概念
贪心算法是指在对问题进行求解时,总是做出在当前看来是最好的选择。也就是说,不是从整体最优上加以考虑,他所做出的仅仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后不会影响以前的状态,只与当前的状态有关。
所以对采用的贪心策略一定要仔细分析其是否满足无后效性。
二、基本思路
1、建立数学模型来描述问题 //找到解题思路(往往最难)。
2、把求解的问题分解为若干个子问题。
3、对每一个子问题求解,找到子问题的局部最优解。
4、把子问题的解局部最优解合成原来解问题的一个解。
三、使用问题
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
四、实现框架
从问题的某一初始解出发;
while(能朝给定总目标前进一步)
{
利用可行的策略,求出可行解的一个解元素;
}
由所有解元素合成问题的一个可行解;
五、贪心策略的选择
因为贪心算法只能通过解局部最优解的策略来达到全局最优解,因此,一定要注意判断问题是否适合用贪心策略,找到的解是否是问题的最优解。

六、例题解析
1、删数问题
输入一个高精度正整数n,删掉其中任意s个数字后剩下的数字按原左右次序组成一个新的正整数。求组成的新的正整数最小。
分析:
贪心策略为每删除一个数都是剩下的数最小。即从高位到低位检索,如果数字递增,则删除最后一位,否则删除第一个递减区间的第一位。
例如:n=175438
s=4
删数过程如下:
n=175438 //删掉7;
=15438 //删掉5;
=1438 //删掉4;
=138 //删掉8;
=13 //解为13;

for(int i=1;i<=s;i++)//一共要删除s个字符
     {
          for(int j=0;j<len-1;j++)//从串首开始找,len是n的长度
          if(n[j]>n[j+1])//找到第一个符合条件的
          {
               for(int k=j;k<len-1;k++)//删除字符n的第j个字符,后面的字符往前整
               n[k]=n[k+1];
               break;
           }
           len--;//长度减1
     }
           int j=0,m=len;
           while(n[j]=='0'&&m>1)//删除串首无用的0,输出答案
           {j++;m--;}
           for(int i=j;i<len;i++)
           cout<<n[i];

2、活动安排(典型贪心算法)
给出多组(begin,end),在一条时间线上有几组能同时进行。

#include <iostream>
#include <algorithm>
using namespace std;
struct pro
{
    int begin,end;
};
bool cmp(pro a,pro b)
{
    return a.end<b.end;
}
int main()
{
    int n;
    pro x[101];
    int sum[101]={0};
    int count=0;
    while(cin>>n)
    {
        if(n==0)break;
        for(int a=0;a<n;a++)
            cin>>x[a].begin>>x[a].end;
        sort(x,x+n,cmp);
        for(int a=0,t=-1;a<n;a++)
        {
            if(t<=x[a].begin)
            {
                sum[count]++;
                t=x[a].end;
            }
        }
        count++;
    }
    for(int a=0;a<count;a++)
        cout<<sum[a]<<endl;
        return 0;
}

活动安排问题只求一组最多场次
升级版:求多次最多场次即没组最多场次算一次。

#include <iostream>
#include <algorithm>
using namespace std;
struct pro
{
    int l,w;
    bool s;//这里需要比上面多一个bool类型,用来判断非每组数据是否被检索过
};
bool cmp(pro a,pro b)
{
    return a.l<b.l;
}
int main()
{
    int t;
    pro x[10000];
    int n;
    int sum;
    cin>>t;
    while(t--)
    {
        cin>>n;
         sum=0;
        for(int a=0;a<n;a++)
            {cin>>x[a].l>>x[a].w;x[a].s=false;}
            sort(x,x+n,cmp);
            int nn=n;
            while(nn)//这里的nn用来控制找了几次最多场次
            {
                int ans=-1;
                int de=0;
                for(int b=0;b<n;b++)
                    if(x[b].s==false)
                if(ans<=x[b].w)
                {
                    de++;//每多一个场次,总场次减1
                    x[b].s=true;
                    ans=x[b].w;
                }
                nn-=de;
                sum++;//计算最多场次数
            }cout<<sum<<endl;
    }
    return 0;
}

3、贪心问题要灵活运用“指针”。
例如贪心作业第四题 用木板铺泥地。
首先将给出的泥地的每组(begin,end)按照end或begin从小到大排序。
初始:point指向数据外。
(1)第一次的point一定是小于begin的,接下来如果point小于begin,那么就说明上一次铺的木板没有影响到本次,则需要重新定义point。
(2)如果point大于end,则要更新end的位置。
(3)如果上一次铺的木板影响到本次(即point>=begin),len变为end-point从此处给point加值。

for(int a=0;a<n;a++)
    {
        if(point>x[a].d)continue;//更新end位置
        if(point>x[a].p)//上一次铺的木板影响到了本次
        {
            int len=x[a].d-point;//len变为end-point
            if(len%l==0){sum+=len/l;point+=len;}
            else {sum+=(len/l+1);point+=(len/l+1)*l;}
        }
        else {
            int len=x[a].d-x[a].p;
            if(len%l==0)
            {
               point=x[a].p+len;
               sum+=len/l;
            }
            else 
            {
                point=x[a].p+(len/l+1)*l;
                sum+=(len/l+1);
            }
            }//上一次的模板没有影响到本次,需要重新定义point
    }

4、性价比类问题
此类问题需要按照性价比排序,根据要求先做性价比高/低的事。
典型背包问题

#include <iostream>
#include <algorithm>
using namespace std;
struct bag
{   
    int w,v;
    double c;
}x[1000];
bool cmp(bag a,bag b)
{
   return a.c>b.c;
}//按性价比排序
int main()
{
   int n,m;
   cin>>n>>m;
   for(int a=0;a<n;a++)
   {
      cin>>x[a].w>>x[a].v;
      x[a].c=(x[a].v*1.0)/x[a].w;
   }
   sort(x,x+n,cmp);
   double ans=0;//用来存储价值
   for(int a=0;a<n;a++)
   {
   if(m<=0)break;//背包空间小于0
   if(x[a].w<=m)
   {ans+=x[a].v*1.0;
   m-=x[a].w;}
   else 
   {
   ans+=m*x[a].c;
   m=0;
   }
   }
   cout<<ans<<endl;
}

心得体会
本周最主要学习了贪心算法,这仅仅是ACM算法家族中的一个算法,但通过一周的做题,我深深感受到了ACM的威力。不仅算法思路难找,而且实现的代码也不好想。即便作业上标明了贪心算法,可在做题的过程中总是不知从哪下手。这让我认识到ACM最注重的不是编码能力,而是在考验我们的思维能力。想要做出一道题,不能直接到开电脑就开始一步步的往电脑上敲代码,而是应该在读懂题目后首先思构出解题方法与解题步骤,需要先在本子上自己模拟一遍程序。
虽然本周题目很难,但我也学到了很多东西,学到了一些解题的思路,也让我的基本遍程能力得到了提升。在这周做题的过程中,我感触最深的一个词就是“区间”。基本上每道题都会涉及到区间,通过一周的练习,我感觉以后即使碰到的不是关于贪心算法的区间问题,我也能应对。
这一周的学习是我的一个开始,让我慢慢提升自身能力,同时也让我认识到自己的渺小。当真正比赛的时候,我恐怕很难ac出题目。这让我认清了现实,自己的能力还远远不足。所以在将来的学习中我必须更加刻苦,更加努力。去接受这个充满挑战的过程!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值