贪心算法小结

目录

贪心算法

例题

活动安排问题

背包问题

删数问题

多处最优服务次序


贪心算法

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

贪心算法不是从整体上考虑问题,而是在每一步选择中都采取在当前状态下最优的选择,即希望通过问题的局部最优解求出整个问题的最优解。

这种策略是一种很简洁的方法,对许多问题它能产生整体最优解,但不能保证总是有效,因为它不是对所有问题都能得到整体最优解。

如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。

利用贪心策略解题,需要解决两个问题:

  • (1)该题是否适合于用贪心策略求解;
  • (2)如何选择贪心标准,以得到问题的最优/较优解。

例题

活动安排问题

1.描述

设有n个活动的集合E={1,2,…,n},每个活动都要使用同一资源,如演讲会场等,而在同一时间段内只有一个活动能使用这一资源。

每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si<fi。如果选择了活动i,则在[si ,fi )内占用资源。

若区间[si ,fi )与区间[sj,fj )不相交,则称活动i与活动j是相容的。

活动安排问题就是在所给的活动集合中选出最大的相容活动子集合。

此时追求的最优是在最短的时间内安排最多的活动

2.思路

按照起止时间排序:有可能遇到初始时间很早,但结束时间很晚的活动。不合适

按照终止时间排序:优先选择较早终止且不与前面活动相交的活动

3.代码

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
struct action{
    int s;//起始时间
    int f;//结束时间
    int index;//活动的变化
};

bool cmp(const action &a,const action &b){
    if(a.f<=b.f)
        return true;
    return false;
}

void GreedySelector(int n,action a[],bool b[]){
    b[1]=true;//选中排序后的第一个活动
    int preEnd=1;//记录最近一次加入集合b的活动
    for(int i=2;i<=n;i++)
        if(a[i].s>=a[preEnd].f){
            b[i]=true;
            preEnd=i;
        }

}
int main()
{
    action a[1001];
    int n;
    scanf("%d",&n);
    bool b[n+1];
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&a[i].s,&a[i].f,&a[i].index);
        b[i]=false;
    }

    sort(a,a+n+1,cmp);
    GreedySelector(n,a,b);
    for(int i=1;i<=n;i++)
        if(b[i])
            printf("%d\n",a[i].index);
    return 0;
}

背包问题

1.描述

给定一个载重量为M的背包,考虑n个物品,其中第i个物品的重量 ,价值wi (1≤i≤n),要求把物品装满背包,且使背包内的物品价值最大。

  • 如果物品不可以分割,称为0—1背包问题(动态规划),可以参考我另一篇博客
  • 如果物品可以分割,则称为背包问题(贪心算法)。

2.思路

有3种方法来选取物品:

  • (1)当作0—1背包问题,用动态规划算法,获得最优值220;
  • (2)当作0—1背包问题,用贪心算法,按性价比从高到底顺序选取物品,获得最优值160。由于物品不可分割,剩下的空间白白浪费,不是最优。
  • (3)当作背包问题,用贪心算法,按性价比从高到底的顺序选取物品,获得最优值240。由于物品可以分割,剩下的空间装入物品3的一部分,而获得了更好的性能。

3.代码

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
struct bag{
    int w;//物品的重量
    int v;//物品的价值
    double c;//性价比
    double x;//此物品装入背包的量
    int index;
};

bool cmp(bag a,bag b){
    if(a.c>=b.c)
        return true;
    return false;
    //return a.c>=b.c;
}

double pack(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;
        a[a[i].index].x=1.0;
        i++;
    }
    //把物品分割后装满背包
    if(i<n){
        a[a[i].index].x=1.0*cleft/a[i].w;
        b+=a[a[i].index].x*a[i].v;
    }
    return b;
}

int main()
{
    bag a[1001];
    int n,c;
    scanf("%d%d",&n,&c);
    for(int i=0;i<n;i++){
        scanf("%d%d%lf%d",&a[i].w,&a[i].v,&a[i].c,&a[i].index);
        a[i].x=0;
    }
    sort(a,a+n,cmp);
    printf("%f\n",pack(n,a,c));
    for(int i=0;i<n;i++)
        if(a[i].x!=0)
        printf("%d:%f\n",a[i].index,a[i].x);
    return 0;
}

删数问题

1.描述

给定n位正整数a,去掉其中任意k≤n个数字后,剩下的数字按原次序排列组成一个新的正整数。对于给定的n位正整数a和正整数k,设计一个算法找出剩下数字组成的新数最小的删数方案。

输入:第1行是1个正整数a,第2行是正整数k。

输出:对于给定的正整数a,编程计算删去k个数字后得到的最小数。

2.思路

n位数a,n可能特别大,因此可以用字符串存放,a表示为x1x2… xn,要删去k位数,使得剩下的数字组成的整数最小。
将该问题记为T,最优解A=xi1 xi2…xim (i1<i2<..<im,m=n-k),在删去k个数后剩下的数字按原次序排成的新数,其最优值记为N。

采用最近下降点优先的贪心策略:即x1<x2<... <xi-1<xi,如果xi+1<xi(下降点),则删去xi,即得到一个新的数且这个数为n-1位中最小的数N,可表示为x1x2…xi-1 xi+1...xn。

显然删去1位数后,原问题T变成了对n- 1位数删去k- 1个数的新问题T'
新问题和原问题性质相同,只是问题规模由n减小为n-1,删去的数字个数由k减少为k-1。
基于此种删除策略,对新问题T’,选择最近下降点的数继续进行删除,直至删去k个数为止。

3.代码

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
    string a;
    int k,i;
    cin>>a>>k;
    if(k>=a.size())
        a.erase();
    else
        while(k>0){
            i=0;
            while(a[i]<=a[i+1])
                i++;
            if(i<a.size()-1){
                //cout<<"a:"<<a<<endl;
                a.erase(i,1);//取出a[i]
                k--;
            //针对12345这种情况,删5-4-3……
            }else if(i == a.size()-1){
               a.erase(i,1);
               k--;
            }
            //cout<<"k:"<<k<<endl;
        }
    while(a.size()>1&&a[0]=='0')
        a.erase(0,1);
    cout<<a<<endl;
    return 0;
}


 

多处最优服务次序

1.描述

设有n个顾客同时等待一项服务,顾客i需要的服务时间为ti,1≤i≤n,共有s处可以提供此项服务。应如何安排n个顾客的服务次序才能使平均等待时间达到最小?平均等待时间是n个顾客等待服务时间的总和除以n。

给定的n个顾客需要的服务时间和s的值,编程计算最优服务次序。

输入:第一行有2个正整数n和s,表示有n个顾客且有s处可以提供顾客需要的服务。接下来的一行中,有n个正整数,表示n个顾客需要的服务时间。

输出:最小平均等待时间,输出保留3位小数。

2.思路

假设原问题为T,并已经知道某个最优服务序列,即最优解为A={t1,t2....tn,其中ti为第i个用户需要的服务时间,则每个用户等待时间T为:
T1=t1;
T2=t1 + t2;
Tn=t1 + t2+...+tn
那么总的等待时间,即最优值N为:
N=nt1 + (n - 1)t2+…+2tn-1+tn
平均等待时间是n个顾客等待时间的总和除以n,故本题实际上就是求使顾客等待时间的总和最小的服务次序。

贪心策略:对服务时间最短的顾客先服务。

首先对需要服务时间最短的顾客进行服务,即做完第一次选择后,原问题T变成了需对n-1个顾客服务的新问题T’。 新问题和原问题相同,只是问题规模由n减小为n-1。

3.代码

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

//顾客等待的队列为client,提供服务的窗口s个
double greedy(int client[],int s,int n){
    //服务窗口的顾客等待时间
    int service[s+1];
    //服务窗口顾客等待时间的总和
    int sum[s+1];
    //按顾客的服务时间升序排序
    sort(client,client+n);

    //初始化
    memset(service,0,sizeof(service));
    memset(sum,0,sizeof(sum));

    //贪心算法
    int i=0;//顾客的指针
    int j=0;//窗口的指针
    while(i<n){
        service[j]+=client[i];
        sum[j]+=service[j];
        i++;j++;
        if(j==s)
            j=0;
    }
    double t=0;
    for(i=0;i<s;i++)
        t+=sum[i];
    t/=n;
    return t;
}

int main()
{
    int client[1000];
    int s,n;
    scanf("%d%d",&n,&s);//顾客数,窗口数
    for(int i=0;i<n;i++)
        scanf("%d",&client[i]);

    printf("%f",greedy(client,s,n));
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值