算法之贪心

贪心算法 greedy algorithm

目录
  • 贪心算法的简介
  • 1.线段覆盖
  • 2.区间选点
  • 3.区间覆盖

贪心算法简介

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
核心:根据题意选取一种量度标准
解题要点
贪心算法是指:在每一步求解的步骤中,它要求“贪婪”的选择最佳操作,并希望通过一系列的最优选择,能够产生一个问题的(全局的)最优解。
  贪心算法每一步必须满足一下条件:
  1、可行的:即它必须满足问题的约束。
  2、局部最优:他是当前步骤中所有可行选择中最佳的局部选择。
  3、不可取消:即选择一旦做出,在算法的后面步骤就不可改变了。
解题步骤
解题的一般步骤是:
1.建立数学模型来描述问题;
2.把求解的问题分成若干个子问题;
3.对每一子问题求解,得到子问题的局部最优解;
4.把子问题的局部最优解合成原来问题的一个解。

简单举例

最少硬币

有1、2、5、10、20、50、100七种面值的硬币,要支付指定的金额,问怎么支付所用的硬币个数最少。
这是一个非常日常化的问题,马上我们会想到,尽可能先用大面值的硬币,就能使支付的硬币尽可能少。这就是“贪心选择”。


经典例题

1.线段覆盖
NYOJ  14  会场安排问题
 有n个活动要在小礼堂举行,小礼堂同一时刻只能由一个活动使用,要使举办尽量多的活动。i活动举行的时间段为[si,ei]

2
1 10
10 11

贪心思路: 用贪心法的话思想很简单:活动越早结束,剩余的时间是不是越多?那我就找最早结束的那个活动,找到后在剩下的活动中再找最早结束的不就得了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBLTys5R-1572958963662)(evernotecid://2C354EAE-828A-4D61-888B-9453DC360564/appyinxiangcom/25418762/ENResource/p300)]

题解如下

#include<stdio.h>
#include<algorithm>
using namespace std;

struct Node
{
    int s,e;
};
bool cmp_sort(Node a,Node b)
{
    if(a.e==b.e)
    {
        return a.s>b.s;
    }
    return a.e<b.e;

}

int main()
{
    int n;
    scanf("%d",&n);
    Node node[n];
    for(int i=0;i<n;i++)
    {
        scanf("%d%d",&node[i].s,&node[i].e);
    }
    sort(node,node+n,cmp_sort);
    int ans=1;
    int t=0;
    for(int i=1;i<n;i++)
    {
        if(node[i].s>node[i].e)
        {
            ans++;
            t=i;
        }
    }
    printf("%d",ans);



    return 0;
}


2.区间选点

找点
Description

上数学课时,老师给了LYH一些闭区间,让他取尽量少的点,使得每个闭区间内至少有一个点。但是这几天LYH太忙了,你们帮帮他吗?


Input
多组测试数据。

每组数据先输入一个N,表示有N个闭区间(N≤100)。

接下来N行,每行输入两个数a,b(0≤a≤b≤100),表示区间的两个端点。


Output
输出一个整数,表示最少需要找几个点。


Sample Input 1 
4
1 5
2 4
1 4
2 3
3
1 2
3 4
5 6
1
2 2
Sample Output 1
1
3
1

贪心思路

这一题思路与上一题相同,只是变变了样子

题解如下

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;

struct Activity
{
    int s,e;
};
bool cmp_sort(Activity a,Activity b)
{
    if(a.e==b.e)
    {
        return a.s>b.s;
    }
    return a.e<b.e;
}

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        Activity aty[n];
        for(int i=0;i<n;i++)
        {
            scanf("%d%d",&aty[i].s,&aty[i].e);
            
        }
        sort(aty,aty+n,cmp_sort);
        int aty_count=1;
        int last_can_aty_end_time=aty[0].e;//第一个活动一定举办,
        for(int i=1;i<n;i++)        //所以上个能举办的活动结束时间为aty[9].e
        {                            //所以for循环要从第二个开始进行判断
            if(aty[i].s>last_can_aty_end_time)
            {
                aty_count++;
                last_can_aty_end_time=aty[i].e;  //更新结束时间。
            }
        }
        printf("%d\n",aty_count);
    }

    return 0;
}


区间覆盖类(一)

Description

好吧。这道题的目的在于让大家认识贪心问题的三个区间经典问题:区间选点问题,区间覆盖问题,以及该题的选择不相交区间问题。有许多的贪心问题可以转化为这三类的问题。

那么,对于该问题。就是给一系列的区间,求最多的区间,要求区间个数最多,这些区间不相交,需要注意的是这些区间都是闭区间。


Input
第一行一个数n为区间个数(n<=1000)

接下来有n行,每行有两个数a,b分别为区间的两个端点,a,b在int范围。

EOF结尾。


Output
输出如样例所示


Sample Input 1 
2
1 10
10 11
3
1 10
10 11
11 20
Sample Output 1
Case 1:
1.
Case 2:
2.
Hint
**注意,A完这道题,如果你对上面的三个问题还没有太理解,请去学会。**

贪心思路:这一题与前两题思路相同,不过这一题给的一对端点,不知哪个大,那个小,需要单独进行一下判断。
题解如下

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;

struct Activity
{
    int s,e;
};
bool cmp_sort(Activity a,Activity b)
{
    if(a.e==b.e)
    {
        return a.s>b.s;
    }
    return a.e<b.e;
}

int main()
{
    int n;
    int counter=1;
    while(~scanf("%d",&n))
    {
        Activity aty[n];
        for(int i=0;i<n;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            if(a>b)
            {
                int temp=a;
                a=b;
                b=temp;
            }
            aty[i].s=a;
            aty[i].e=b;

        }
        sort(aty,aty+n,cmp_sort);
        int aty_count=1;
        int last_can_aty_end_time=aty[0].e;//第一个区间一定要取到,
        for(int i=1;i<n;i++)        
        {                            //所以for循环要从第二个区间开始进行判断
            if(aty[i].s>last_can_aty_end_time)
            {
                aty_count++;
                last_can_aty_end_time=aty[i].e;  //更新结束时间。
            }
        }
        printf("Case %d:\n%d.\n",counter,aty_count);
    }

    return 0;
}

区间覆盖 (二)
给定一个长度为m的区.0间,再给出n条线段的起点和终点(注意这里是闭区间),求最少使用多少条线段可以将整个区间完全覆盖 样例: 
区间长度8,可选的覆盖线段[2,6],[1,4],[3,6],[3,7],[6,8],[2,4],[3,5]
每次选择从当前起点能覆盖到的最长的区间

题解如下

#include<stdio.h>
#include<algorithm>
using namespace std;

struct Line
{
    int s,e;
};

bool cmp_sort(Line a,Line b)
{
    if(a.s==b.s)
    {
        return a.e>b.e;
    }
    return a.s<b.s;
}

int main()
{
    int m,n;
    scanf("%d%d",&m,&n);
    Line line[n];
    for(int i=0;i<n;i++)
    {
        scanf("%d%d",&line[i].s,&line[i].e);
    }
    sort(line, line+n, cmp_sort);
    int ans=1;
    int temp_end=line[0].e;
    int end_pos=temp_end;
    for(int i=1;i<n;i++)
    {
        if(line[i].s>end_pos)
        {
            ans++;
            end_pos=temp_end;
            temp_end=line[i].e;
        }
        else
        {
            temp_end=max(temp_end,line[i].e);
        }
    }
    if(end_pos<m)
    {
        ans++;
    }
    printf("%d",ans);
    return 0;
}

区间类覆盖(三)

Description

有一块草坪,横向长w,纵向长为h,在它的橫向中心线上不同位置处装有n(n<=10000)个点状的喷水装置,每个喷水装置i喷水的效果是让以它为中心半径为Ri的圆都被润湿。请在给出的喷水装置中选择尽量少的喷水装置,把整个草坪全部润湿。


Input
第一行输入一个正整数N表示共有n次测试数据。

每一组测试数据的第一行有三个整数n,w,h,n表示共有n个喷水装置,w表示草坪的横向长度,h表示草坪的纵向长度。

随后的n行,都有两个整数xi和ri,xi表示第i个喷水装置的的横坐标(最左边为0),ri表示该喷水装置能覆盖的圆的半径。


Output
每组测试数据输出一个正整数,表示共需要多少个喷水装置,每个输出单独占一行。

如果不存在一种能够把整个草坪湿润的方案,请输出0。


Sample Input 1 
2
2 8 6
1 1
4 5
2 10 6
4 5
6 5
Sample Output 1
1
2


贪心思路

这一题首先要判断所给,的某个喷水器能否使用,如果不能使用,就把它标记为假值(以后直接跳过不考虑),而其他的喷水器都可用,然后要通过喷水器的横坐标与半径,求出该喷水器的作用区间,然后就通过两层for循环去找最优解。
题解如下

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
struct None
{
    int center;
    double  x,y;  //x 起始坐标 ,y区间结束坐标
    int r; 
    bool can_use;  //喷水器能否使用
};


int main()
{
    int t;
    scanf("%d",&t);
    while (t--)
    {
        int start_place=0;  //初始化一开始的区间结束位置
        int n,w,h;
        scanf("%d%d%d",&n,&w,&h);
        None none[n];
        for(int i=0;i<n;i++)
        {
            scanf("%d%d",&none[i].center,&none[i].r);
            if(2*none[i].r<=h)
            {
                none[i].can_use=0;
            }
            else
            {
                none[i].can_use=1;
            }
       //根据横坐标去去转化成区间
       none[i].x=none[i].center-sqrt(none[i].r*none[i].r-(h/2.0)*(h/2.0));
       none[i].y=none[i].center+sqrt(none[i].r*none[i].r-(h/2.0)*(h/2.0));

        }
        int sum_count=0;  //总数
        int flag_1;  //两个判断标志
        int flag_2;
        double max_end;  //某种情况最有区间结束位置
        for( ; ; )   
        {
            flag_1=0;
            flag_2=0;
            int time=0;
            for(int j=0;j<n;j++)
            {
                if(none[j].x<=start_place&&none[j].y>start_place&&none[j].can_use)//判断某个区间是否是最有区间
                {
                        
                    none[j].can_use=0;
                    if(time==0)  //初始化给max_end赋一个初始值
                    {
                        max_end=none[j].y;
                        time++;
                        flag_1=1;
                    }
                    else if(max_end<=none[j].y)
                    {
                        max_end=none[j].y;
                        flag_1=1;
                    }
                }
            }
            if(flag_1==1)
            {
               // printf("#\n");
                sum_count++;
                start_place=max_end;
                if(start_place>=w)
                {
                    flag_2=1;
                    break;
                }
            }
            else if(flag_1==0)
            {
                break;
            }
        }

        if(flag_1==1&&flag_2==1)  // 两个条件同时满足才可以,全覆盖
        {
            printf("%d\n",sum_count);
        }
        else
        {
            printf("0\n");
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值