贪心算法专题

贪心本质

一个贪心算法总是做出当前最好的选择,也就是说,它期望通过局部最优选择从而得到全局最优的解决方案。

——《算法导论》

贪心算法的特点

贪心算法在解决问题只根据当前已有的信息就做出选择,而且一旦做出了选择,这个选择都不会改变。换言之,贪心算法并不是从整体最优考虑,它所做出的选择只是在某种意义上的局部最优。贪心算法能得到许多问题的整体最优解或整体最优解的近似解。因此,贪心算法在实际中得到大量的应用。

在贪心算法中,我们需要注意以下几个问题。

  1. 一旦做出选择,不可以反悔。
  2. 有可能得到的不是最优解,而是最优解的近似解。
  3. 选择什么样的贪心策略,直接决定算法的好坏。

这里补充一下,贪心算法所选择的贪心策略数学证明是非常困难的,一般都是采用对数器的技巧来验证。

贪心算法设计步骤

  1. 贪心选择
    所谓贪心选择性质是指原问题的整体最优解可以通过一系列的局部最优选择得到。应用同一规则,将原问题变为一个相似的但规模更小的子问题,而后的每一步都是当前最佳的选择。这种选择依赖于已做出的选择,但不依赖于未做出的选择。运用贪心策略解决的问题在程序的运行过程中无回溯过程。
  2. 最优子结构
    当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题是否可用贪心算法求解的关键。例如原问题S={a1,a2,…,ai,…,an},通过贪心选择选出一个当前最优解{ai}之后,转化为求解子问题S−{ai},如果原问题的最优解包含子问题的最优解,则说明该问题满足最优子结构性质。
    《贪心算法专题》

贪心算法秘籍

我们已经知道了具有贪心选择和最优子结构性质就可以使用贪心算法,那么如何使用呢?

  1. 贪心策略
    首先要确定贪心策略,选择当前看上去最好的一个方案。例如,挑选苹果,如果你认为个大的是最好的,那你每次都从苹果堆中拿一个最大的,作为局部最优解,贪心策略就是选择当前最大的苹果;如果你认为最红的苹果是最好的,那你每次都从苹果堆中拿一个最红的,贪心策略就是选择当前最红的苹果。因此根据求解目标不同,贪心策略也会不同。
  2. 局部最优解
    根据贪心策略,一步一步地得到局部最优解。例如,第一次选一个最大的苹果放起来,记为a1,第二次再从剩下的苹果堆中选择一个最大的苹果放起来,记为a2,以此类推。
  3. 全局最优解
    把所有的局部最优解合成为原来问题的一个最优解(a1,a2,…)。

贪心算法中最难的部分是没有套路的,是贪心策略,比如 今年暑假不AC这道题,就是要以节目结束时间为贪心策略的,具体怎么的出的我只能告诉你是想出来的,因为不论是节目时长,或者节目开始时间我总能举出反例,而以结束时间作为排序策略是可以得出正确答案的,数学方面很难证明,就通过对数器验证,同学们在做题的时候只需要记住选择什么贪心策略而不用把大量精力用在证明上。
上面的说明文字参照了这位大神的博客http://blog.csdn.net/rainchxy/article/details/78708301

具体题目讲解

题目地址https://www.nowcoder.com/questionTerminal/d2cced737eb54a3aa550f53bb3cc19d0

餐馆

时间限制:1秒 空间限制:65536K

某餐馆有n张桌子,每张桌子有一个参数:a 可容纳的最大人数; 有m批客人,每批客人有两个参数:b人数,c预计消费金额。 在不允许拼桌的情况下,请实现一个算法选择其中一部分客人,使得总预计消费金额最大
输入描述:
输入包括m+2行。 第一行两个整数n(1 <= n <= 50000),m(1 <= m <= 50000) 第二行为n个参数a,即每个桌子可容纳的最大人数,以空格分隔,范围均在32位int范围内。 接下来m行,每行两个参数b,c。分别表示第i批客人的人数和预计消费金额,以空格分隔,范围均在32位int范围内。

输出描述:
输出一个整数,表示最大的总预计消费金额
示例1
输入
3 5 2
4 2
1 3
3 5
3 7
5 9
1 10
输出
20

示例2
input
4 6
12 1 4 7
11 3
3 10
35 10
5 9
12 10
6 7
output
29
思路:经典贪心问题
先根据收益最大值降序排列,如果收益相同就按人数降序排列
然后暴力累加(这里可以用二分优化一下,就可以ac,我偷懒没做)
在遍历的时候要注意这两点 允许有桌子没坐满(每一波客人都超过桌子最大容量)
已经使用的桌子不允许被再次使用


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class 餐馆 {//40% pass limit time out
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int n=input.nextInt();
        int m=input.nextInt();
        int table[]=new int[n];
        for (int i = 0; i < table.length; i++) {
            table[i]=input.nextInt();
        }
        ArrayList<Customers> cs=new ArrayList<Customers>();
        for (int i = 0; i < m; i++) {
            Customers c=new Customers();
            c.num=input.nextInt();
            c.cost=input.nextInt();
            c.weight=(double)c.cost/(double)c.num;
            c.visit=false;
            cs.add(c);
        }
        Arrays.sort(table);
        cs.sort(new Comparator<Customers>() {
            @Override
            public int compare(Customers c1, Customers c2) {
                if (c2.cost!=c1.cost) {
                    return c2.cost>c1.cost?1:-1;
                }else {
                    return c2.num>c1.num?1:-1;
                }

            }
        });
// for (Customers c : cs) {
// System.out.println(c.num+" "+c.cost);
// }
        int sum=0;
        for (int i = 0; i < table.length; i++) {
            boolean flag=true;
            for (int j = 0; j < cs.size()&&flag; j++) {
                if (table[i]>=cs.get(j).num&&(cs.get(j).visit==false)) {
                    sum+=cs.get(j).cost;
                    cs.get(j).visit=true;
                    flag=false;
                }else {
                    continue;
                }               
            }
        }
        System.out.println(sum);
    }

}

突击战(Commando War,UVa 11729)

修改为单输出版本了
你有n个部下 每个部下完成一项任务

第i个部下需要你花Bi分钟交代任务,然后他会独立的无间断的执行Ji分钟后完成任务。你需要选择交代任务的顺序,使得所有任务尽早执行完毕。
注意,不能同时给两个部下交代任务,但部下们可以同时执行各自的任务。

样例输入:
3
2 5
3 2
2 1
样例输出:
8
样例输入:
3
3 3
4 4
5 5
样例输出:
15
样例输入:
3
3 20
4 4
5 5
样例输出:
20
分析,简单的动态规划
优先按照执行时间降序排列,如果执行时间一样安排时间升序排
建议画线段图分析
相关文章
http://blog.csdn.net/wdays83892469/article/details/78631887

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class 突击战 {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int n=input.nextInt();
        Soldier ss[]=new Soldier[n];
        for (int i = 0; i < ss.length; i++) {
            ss[i]=new Soldier();
            ss[i].t1=input.nextInt();
            ss[i].t2=input.nextInt();
        }
        Cmp cmp=new Cmp();
        Arrays.sort(ss,cmp);
// for (int i = 0; i < ss.length; i++) {//排序结果输出测试
// System.out.println(ss[i].t1+" "+ss[i].t2);
// }
        int s=0;
        int ans=0;
        for (int i = 0; i < ss.length; i++) {//s是每次安排任务的累加时间
            s+=ss[i].t1;
            ans=Math.max(ans, s+ss[i].t2);//贪心选取最大值 安排任务的时间累加和+本次任务的执行时间 选其中最大的
        }
        System.out.println(ans);
    }
}
class Cmp implements Comparator<Soldier>{

    @Override
    public int compare(Soldier s1, Soldier s2) {//从安排时间排序
        if (s1.t2!=s2.t2) {
            return s1.t2<s2.t2?1:-1;
        }else {
            return s1.t1>s2.t1?1:-1;
        }

    }

}
class Soldier {
    int t1;
    int t2;
}

题目地址
http://poj.org/problem?id=3646

勇者斗恶龙(The Dragon of Loowater,UVa 11292)

Once upon a time, in the Kingdom of Loowater, a minor nuisance turned into a major problem.
The shores of Rellau Creek in central Loowater had always been a prime breeding ground for geese. Due to the lack of predators, the geese population was out of control. The people of Loowater mostly kept clear of the geese. Occasionally, a goose would attack one of the people, and perhaps bite off a finger or two, but in general, the people tolerated the geese as a minor nuisance.
One day, a freak mutation occurred, and one of the geese spawned a multi-headed fire-breathing dragon. When the dragon grew up, he threatened to burn the Kingdom of Loowater to a crisp. Loowater had a major problem. The king was alarmed, and called on his knights to slay the dragon and save the kingdom.
The knights explained: “To slay the dragon, we must chop off all its heads. Each knight can chop off one of the dragon’s heads. The heads of the dragon are of different sizes. In order to chop off a head, a knight must be at least as tall as the diameter of the head. The knights’ union demands that for chopping off a head, a knight must be paid a wage equal to one gold coin for each centimetre of the knight’s height.”
Would there be enough knights to defeat the dragon? The king called on his advisors to help him decide how many and which knights to hire. After having lost a lot of money building Mir Park, the king wanted to minimize the expense of slaying the dragon. As one of the advisors, your job was to help the king. You took it very seriously: if you failed, you and the whole kingdom would be burnt to a crisp!
Input
The input contains several test cases. The first line of each test case contains two integers between 1 and 20000 inclusive, indicating the number n of heads that the dragon has, and the number m of knights in the kingdom. The next nlines each contain an integer, and give the diameters of the dragon’s heads, in centimetres. The following mlines each contain an integer, and specify the heights of the knights of Loowater, also in centimetres.
The last test case is followed by a line containing:
0 0

Output
For each test case, output a line containing the minimum number of gold coins that the king needs to pay to slay the dragon. If it is not possible for the knights of Loowater to slay the dragon, output the line:
Loowater is doomed!

我把多输入改成单输入 把我的代码直接提交是不行的,要略微改动。主要考察贪心思想,不是UVa的参考答案

题意大概就是一条恶龙有很多个头,但每个头能力有大有小
我们要雇佣一些勇者把恶龙怼死,一个勇者只能砍一个头,而且只能砍能力小于等于自己数值的头
每个勇者的能力值就是佣金,算如何花费最少钱。
如果勇者无法把恶龙怼死,就输出Loowater is doomed!
input
2 3
5
4
7
output
11

input
2 2
5 5
10
4
output
Loowater is doomed!
思路
典型贪心问题,排序后贪心。需要注意的点是必须把所有头砍完,就可以跳出循环了。在算法里已经把勇士少于恶龙头数的情况包含了 (区别于餐馆那道题,那道题如果有很多拨人,有一个桌子容纳量太小每拨人都坐不下是允许的)

import java.util.Scanner;

public class 勇者斗恶龙 {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int d=input.nextInt();
        int h=input.nextInt();
        int dragon[]=new int[d];
        int hero[]=new int[h];
        for (int i = 0; i < dragon.length; i++) {
            dragon[i]=input.nextInt();          
        }
        for (int i = 0; i < hero.length; i++) {
            hero[i]=input.nextInt();            
        }
        Arrays.sort(hero);
        Arrays.sort(dragon);
        int index=0;
        int cur=0;//记录砍掉的头数
        int cost=0;
        for (int i = 0; i < dragon.length; i++) {
            if (hero[index]>=dragon[i]) {
                cost+=hero[index];//已砍,雇佣这个勇者
                cur++;//已砍的头数++
                index++;//下一个勇者登场
            }
            if (index==dragon.length-1) {
                break;
            }
        }
        System.out.println(cur<dragon.length?"Loowater is doomed!":cost);
    }
}

今年暑假不AC

题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=2037

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

/* 样例输入: 12//十二个电视节目 1 3//开始时间 结束时间 3 4 0 7 3 8 15 19 15 20 10 15 8 18 6 12 5 10 4 14 2 9 0 样例输出: 5 //最多能完整看几个节目 */
 /* 经典贪心问题,对节目用结束时间排序 */
public class 今年暑假不AC {

    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int n=input.nextInt();
        if (n==0){ 
            System.out.println("0");
            return;
        }
        Program problems[]=new Program[n];
        for(int i=0;i<n;i++){
            problems[i]=new Program();
            problems[i].startTime=input.nextInt();
            problems[i].endTime=input.nextInt();
        }
        Cmp2 cmp=new Cmp2();
        Arrays.sort(problems,cmp);//按照结束时间升序排列
        int currentTime=0;//记录当前时间变量初始值为0 
        int ans=0;//答案计数初始值为0 
        for(int i=0;i<n;i++){//按照结束时间升序遍历所有的节目
            if(currentTime<= problems[i].startTime){
                //若当前时间小于等于该节目开始时间,那么收看该在剩余节目里结束时间最早的节目
                currentTime= problems[i].endTime;//当前时间变为该节目结束时间
                ans++;//又收看了一个节目,计数器++ 
            }
        }
        System.out.println(ans);
    }

}
class Cmp2 implements Comparator<Program>{
    @Override
    public int compare(Program p1, Program p2) {
        return p1.endTime-p2.endTime;
    }
}
class Program {    
    int startTime;  //节目开始时间
    int endTime;    //节目结束时间
} 
    原文作者:贪心算法
    原文地址: https://blog.csdn.net/wdays83892469/article/details/79101539
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞