关于贪心算法

贪心算法(Greedy algorithm),又称贪婪算法。是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而使得问题得到全局最优解。

贪心的算法的设计就是要遵循某种规则,不断地选取当前最优解的算法设计方法。

贪心算法基本概念

贪心算法与枚举法的不同之处在于每个子问题都选择最优的情况,然后向下继续进行,且不能回溯,枚举法是将所有情况都考虑然后选出最优的情况。

贪心算法,在对问题求解时,不从整体考虑,而是采用一叶障目的选择方式,只选择某种意义上的局部最优解。并且,贪心算法是没有固定的模板可以遵循的,每个题目都有不同的贪心策略,所以算法设计的关键就是贪心策略的选择。

贪心算法有一个必须要注意的事情。贪心算法对于问题的要求是,所有的选择必须是无后效性的,即当前的选择,不能影响后续选择对于结果的影响。

贪心算法主要适用于最优化问题,如:MST(最小生成树) 问题。有时候贪心算法并不能得到最优答案,但是能得到精确答案的近似答案。有时可以辅助其他算法得到不是那么精确的结果。

适用范围

符合贪心策略:

所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。

所谓的贪心选择性质就是,该问题的每一步选择都在选择最优的情况下能够导致最终问题的答案也是最优。

或者说是无后效性,如果该问题的每一步选择都对后续的选择没有影响,就可以是应用贪心算法。

贪心算法的设计步骤:

  1. 证明原问题的最优解之一可以由贪心选择得到。
  2. 将最优化问题转化为这样一个问题,即先做出选择,再解决剩下的一个子问题。
  3. 对每一子问题一一求解,得到子问题的局部最优解;
  4. 把子问题的解局部最优解合成原来解问题的一个解

找零问题

假设商店老板需要找零 n 元钱。
 钱币的面额有:100 元、50 元、20 元、5 元、1 元、如何找零使得所需钱币的数量最少?
 注意:n 可能为 0,也能为几百元(别问,问就是微信提现来了)

输入说明:
 在第一行给出测试例个数 N。1<=N<=10^5
代表需要找零的钱数。

输出说明:
 有 5 行输出数据,每一行输出数据输出找零的金额与数量,详情看样例。
输入样例:
 365
输出样例: 
100:3
50:1
20:0
5:3
1:0

运行限制:

最大运行时间:1s
最大运行内存:128M

我们都知道从大的钱开始找钱。这就是一种贪心的思想,将大问题转化为一个个小的子问题,每次选择最大的钱数使得总量最小。

代码如下:

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
 
import static java.lang.Integer.max;
 
public class Main {
 
 
  //面值
  static  int t[] = new int[]{100, 50, 20, 5, 1};
 
  //张数
  static int m[] = new int[5];
 
  static void change(int n) {
 
      for (int i = 0; i < 5; i++) {
          m[i] = n / t[i];
 
          n = n % t[i];
 
          //print("%d",n);
      }
 
  }
 
  public static void main(String[] args) {
 
      int N;
 
      Scanner in = new Scanner(System.in);
 
      N = in.nextInt();
 
      change(N);
 
      for (int i = 0; i < 5; i++) {
          System.out.println(String.format("%d:%d", t[i], m[i]));
      }
  }
} 

 活动选择型问题——小 B 的宿舍

小 B 的宿舍楼沿着走廊南北向的两边各有 200 个房间。
 如图所示:
[房间1][房间3][房间5][房间7][房间9 ]...[房间399]
----------------------------------------------
                   走廊
----------------------------------------------
[房间2][房间4][房间6][房间8][房间10]...[房间400]
 
最近,由于转专业和专业分流的原因,宿舍将迎来新的调整,以便组成新的班级后方便管理。
但是由于走廊狭窄,走廊里只能通过两个搬运的物品(可以同向也可以反向),因此必须指定高效的搬运计划。
老师给了每位同学下达了以下要求,让同学们提前收拾好行李,然后给每位同学 10 分钟的时间搬运。
当房间 i 搬运行李到 j 时,i 与 j 之间的走廊都会被占用,但是可以容纳两个不同同学同时搬运。所以,10 分钟之内同一段走廊最多两个人同时搬运,不重叠的走廊也可以同时搬运。
小 B 的老师是个数学老师,经过运筹学一通计算他得到了最优的搬运计划。
虽然计划不唯一,但是最优值唯一,请问这个最短时间是多少?

输入说明:
输入数据有 T 组测试例,在第一行给出测试例个数 T。 
每个测试例的第一行是一个整数 N(1≤N≤200),表示要搬运行李的人数。接下来 N 行,每行两个正整数 s 和 t,表示一个人,将行李是从房间号码 s 移到到房间号码 t。
 输出说明:
 每组输入都有一行输出数据,为一整数 T,表示完成任务所花费的最小时间。
输入样例: 
3
4
10 20
30 40
50 60
70 80
2
1 3
2 200
3
10 100
20 80
30 50
输出样例:
10
10
20

运行限制:

最大运行时间:1s
最大运行内存:128M

 该题属于贪心算法,因为它尽可能使搬运行李同时进行,以便使单独安排的搬运次数最少。这样用的时间最少,即所用最少时间为不能同时搬运行李的次数,进而转化为寻找某一段走廊使用次数最多(贪心标准),由于走廊可以同行 2 人,所以除 2,有余数再加 1 即可,即使最多的搬运次数,再乘以 10,即为最少搬运时间。

不难发现,相对应的两个房间其实是占用一段走廊的,我们可以将将房间号映射为走廊号,然后再考虑上面的解析。

代码如下:

 
import java.util.Scanner;
import static java.lang.Integer.max;
 
 
public class Main {
 
 
  public static void main(String[] args) {
 
 
      Scanner in = new Scanner(System.in);
//搬运次数
      int N;
 
//每次搬运的起点和终点
 
      int from, to;
      int maxAns = 0;
      int T;
 
      T = in.nextInt();
 
      while (T > 0) {
          T--;
          N = in.nextInt();
 
          int[] move = new int[205];
 
          for (int i = 0; i < N; i++) {
 
 
              from=in.nextInt();
              to=in.nextInt();
//将房间号映射为走廊号
              from = (from - 1) / 2;
              to = (to - 1) / 2;
//确保from to) {
                  int temp = from;
                  from = to;
                  to = temp;
              }
 
 
//统计占用走廊情况,并统计最大值
 
              for (int j = from; j <= to; j++) {
                  move[j]++;
                  maxAns = max(maxAns, move[j]);
              }
 
 
          }
          if (maxAns % 2 == 1) maxAns = maxAns / 2 + 1;
          else maxAns /=2; //等价于/2
 
          System.out.println( maxAns * 10);
      }
  }
}

可拆分背包问题——贪心的自助餐

小 B 同学呢,想去吃自助餐,但是他是那种比较节俭的的人,既不想浪费食物,又想尽可能吃的贵一点,他于是私下里做了调查。
 小蓝餐厅的自助餐有 n 种食材,每种食材都有它的价格。
而且也能估计出每一份的重量,所以他列了一个表格。
红烧牛肉  30元    300g
油闷大虾  8元     5g
四喜丸子  4元     8g
三文鱼    5元     3g
排骨      18元    200g
麻辣兔头  20元    120g
高汤海参  40元    70g
扇贝粉丝  8元     32g
牛排      79元    240g
...
现在小 B 想知道在不超过饭量的情况下他到底最多吃多少钱的菜品。
假设自助餐厅的菜品供应同样的菜品每个人最多只能取一份。
小B的饭量假设为 C,单位为 g。
现在请你设计一个程序帮助小 B 计算他的最多吃了多少钱。

输入说明:
 第一行输入 n C(0<=n<=1000)(0<=C<=10000)
其中 n 为菜品数量,C 为小 B 的肚子容量。 
第二行输入两个数 V,W 
 第一个数 V[i] 是第 i 个菜品的价值(0<=v[i]<=10000)
 第二个数 V[i] 是第 i 个菜品的质量(0<=w[i]<=10000)
 输出解法
 
输出一行数据,表示最大的价值,保留三位小数。
输入样例: 
20 1000
1 22
2 43
123 214
12 2
123 432
21 223
22 16
77 49
34 78
34 9
43 677
21 34
23 23
12 56
332 56
21 99
123 545
389 33
12 999
23 88

输出样例:
 1204.114

运行限制:

最大运行时间:1s
最大运行内存:128M

贪心算法的最主要的特征就是无后效性,就像是自助餐这个题目,如果说吃了某一样食物,就不能吃另一个食物了,那么这就有了后效性,那就不能使用贪心算法进行解决问题了。

可拆分背包的一般解法为:

这里有 n 种不同值 v[i] 和权重 w[i] 的对象(如果选择该对象的 w[i] 可以获得值 v[i])。

你有一个容器来挑选它们。你可以根据自己的需要把它们分成任意大小的碎片。可以拾取的对象的最大重量给定为 w。请计算您能得到的最大值。

就像是这个题目,要想吃回本就要捡着贵的吃,但是贵只是一方面,人会饱,所以用价格除以质量所获的价格商才是贪心准则,应按照价格商优先进行选取。

这里因为要整体排序,所以要先创建一个类,然后自定义 cmp 函数,再使用 sort 排序。

代码如下:

package com.company;
 
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
 
import static java.lang.Integer.max;
 
 
public class Main {
 
   static class Food {
 
       public double w;
       public double v;
       public double aver;
 
       Food(){
           w=0;
           v=0;
           aver=0;
       }
   }
 
   static class MyCmp implements Comparator {
 
       @Override
       public int compare(Food a, Food b) {
 
           if (a.aver > b.aver){
               return  -1;
           }
           else return 1;
 
           //助记大于号就是从大到小排序,小于号就是从小到大排序
 
       }
   }
   static Food foods[]=new Food[ 1009];
   public static void main(String[] args) {
 
       Scanner in = new Scanner(System.in);
 
       int n;
       double C;
       double Value = 0;
       n = in.nextInt();
       C = in.nextDouble();
       for (int i = 0; i < n; i++) {
           foods[i]=new Food();
           foods[i].v = in.nextDouble();
           foods[i].w = in.nextDouble();
 
           //求性价比
           foods[i].aver = foods[i].v / foods[i].w;
           //cout << foods[i].aver << endl;
 
       }
 
       //性价比排序
 
       Comparator cmp=new MyCmp();
       Arrays.sort(foods,0,n,cmp);
 
 
       //当背包(肚子)能装下所有物品(菜)时,直接输出所有的物品(菜品)价值之和
       //
       int sum = 0;
       for (int i = 0; i < n; i++) {
           sum += foods[i].w;
       }
       if (sum < C) {
           for (int j = 0; j < n; j++)
               Value += foods[j].v;
           //V = floor(V * 1000.0) / 1000.0;
           System.out.print(String.format("%.3f",Value));
           return ;
       }
 
       //当背包(肚子)不能装下所有物品时应该由性价比的顺序,选择装入的物品
 
       for (int i = 0; i < n; i++) {
           if (foods[i].w <= C) {
               Value = Value + foods[i].v;
               C = C - foods[i].w;
           } else {
               //直接将剩余的C加入即可
               Value = Value + C * foods[i].aver;
               C = 0;
           }
           if (C == 0)
               break;
       }
       //V = floor(V * 1000.0) / 1000.0;
       System.out.print(String.format("%.3f",Value));
       return ;
   }
}

你可能感兴趣的:(学习记录,贪心算法,算法)