数据结构与算法-09贪心算法&动态规划

07-16 1779阅读

贪心算法&动态规划

1 贪心算法介绍

贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。贪心算法通常用于解决优化问题,如最小化成本、最大化收益等。然而,贪心算法并不总是能够得到全局最优解,但它具有直观、高效、易于实现等优点,因此在许多实际问题中得到了广泛应用。

数据结构与算法-09贪心算法&动态规划
(图片来源网络,侵删)

基本思想

  • 贪心算法总是从问题的某一个初始解出发。
  • 在每一步决策中,它总是做出在当前状态下最好或最优的选择,即局部最优解。
  • 通过每一步的局部最优选择,希望能够导致全局最优解。

    贪心算法实现步骤

    • 建立数学模型来描述问题。
    • 把求解的问题分成若干个子问题。
    • 对每一子问题求解,得到子问题的局部最优解。
    • 把子问题的局部最优解合成原来问题的一个解。
    • 示例:找零问题
      • 问题描述:
        • 假设一个自动售货机只能接受1元、5元和10元的纸币或硬币,现在顾客给了售货机N元,售货机需要尽可能多地使用10元、5元和1元的纸币或硬币来找零给顾客。
        • 建立数学模型:
          • 设顾客给出的金额为 N 元,10元、5元和1元的纸币或硬币数量分别为 x, y, z。
          • 我们需要求解的是: x * 10 + y * 5 + z * 1 = N
          • x, y, z 均为非负整数,并且希望 x 尽可能大(因为10元的面值最大),然后在 x 确定的情况下 y 尽可能大,最后 z 用来补足剩余的金额。
          • 把求解的问题分成若干个子问题
            • 尽可能多地使用10元纸币,即 x = N // 10(整除运算)。
            • 接下来,从剩余的金额(N % 10)中尽可能多地使用5元纸币,即 y = (N % 10) // 5。
            • 最后,使用1元纸币来补足剩余的金额,即 z = N % 10 - y * 5。
            • 把子问题的局部最优解合成原来问题的一个解
              • 将 x, y, z 的值组合起来,就得到了找零的纸币或硬币组合。

                贪心算法的适用范围

                • 贪心算法适用于那些具有贪心选择性质和最优子结构性质的问题。
                • 贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择来达到。
                • 最优子结构性质:问题的最优解所包含的子问题的解也是最优的。

                  局限性

                  • 贪心算法并不总是能够得到全局最优解。对于某些问题,贪心算法可能会陷入局部最优解而无法达到全局最优。
                  • 贪心算法的正确性需要证明。对于每一个贪心选择,都需要证明其能够导致全局最优解。

                    优化

                    • 对于一些贪心算法无法直接求解的问题,可以通过一些优化策略或与其他算法结合来求解。
                    • 例如,在求解整数背包问题时,可以使用动态规划算法来求解全局最优解,或者将贪心算法与回溯算法结合使用来求解近似最优解。

                      2 贪心算法例题

                      2.1 会议问题

                      题目:

                      某天早上公司领导找你解决一个问题,明天公司有N个同等级的会议需要使用同一个会议室,现在给你这个N个会议的开始和结束时间,你怎么样安排才能使会议室最大利用?即安排最多场次的会议?

                      示例:

                      会议1:0点~9点:9点之前开始的会议都不行了。

                      会议2:8点~10点

                      会议3:10点~12点:12点

                      会议4:8点~20点

                      结果:最多可以安排两场会议【会议1、会议3】

                      import java.util.ArrayList;
                      import java.util.List;
                      /**
                       * 题目:某天早上公司领导找你解决一个问题,明天公司有N个同等级的会议需要使用同一个会议室,现在给你这个N个会议的开始和结束时间,你怎么样安排才能使会议室最大利用?即安排最多场次的会议?
                       *
                       * eg:
                       * 会议1:0点~9点:9点之前开始的会议都不行了。
                       * 会议2:8点~10点
                       * 会议3:10点~12点:12点
                       * 会议4:8点~20点
                       * 结果:最多可以安排两场会议【会议1、会议3】
                       *
                       * 思路:
                       * 1、将会议根据结束时间进行排序
                       * 2、遍历会议,如果当前会议开始时间大于等于前一个会议的结束时间,则说明可以安排会议,否则不能安排会议
                       */
                      public class MeetingTest {
                          public static void main(String[] args) {
                              List meetings = initInfo(5);
                              meetings.sort(null);
                              int currentTime = 0;
                              for (Meeting meeting : meetings) {
                                  if (meeting.getStart() >= currentTime){
                                      System.out.println(meeting);
                                      currentTime = meeting.getEnd();
                                  }
                              }
                          }
                          public static List initInfo(int size){
                              List list = new ArrayList();
                              Meeting meeting1 = new Meeting(0, 9);
                              list.add(meeting1);
                              Meeting meeting2 = new Meeting(8, 10);
                              list.add(meeting2);
                              Meeting meeting3 = new Meeting(10, 12);
                              list.add(meeting3);
                              Meeting meeting4 = new Meeting(8, 20);
                              list.add(meeting4);
                              return list;
                          }
                      }
                      class Meeting implements Comparable{
                          private int start;
                          private int end;
                          public Meeting(int start, int end) {
                              this.start = start;
                              this.end = end;
                          }
                          public int getStart() {
                              return start;
                          }
                          public int getEnd() {
                              return end;
                          }
                          @Override
                          public int compareTo(Meeting o) {
                              return this.end == o.end ? 0 : this.end  
                      

                      3 动态规划介绍

                      动态规划(Dynamic Programming,简称DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

                      基本思想

                      1. 重叠子问题:在求解过程中,动态规划算法会将每个子问题的解存储起来,当再次需要求解此子问题时,直接返回之前存储的结果,而不是重新计算。这是动态规划算法能够显著提高效率的关键。
                      2. 最优子结构:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构性质。动态规划利用这一性质,通过求解子问题的最优解来构造原问题的最优解。

                      动态规划实现步骤

                      1. 划分阶段:按照问题的时间或空间特征,将问题划分为若干个阶段。每个阶段对应一个决策过程,决策过程的选择不是任意的,它依赖于当前的状态,又影响以后的发展。
                      2. 定义状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。状态的选择要满足无后效性,即“将来与过去无关”,只与当前的状态有关。
                      3. 状态转移方程:根据上一阶段状态和决策来导出本阶段的状态的转移方程,即状态之间的递推关系。
                      4. 求解:按照阶段从前往后(或自底向上)逐步计算各个状态及对应的最优值(或最优策略),直至得到初始状态的最优值(或最优策略)为止。

                      示例说明

                      • 题目:背包问题

                        • 是一个经典的组合优化问题,其描述如下:给定一组物品,每种物品都有自己的重量和价值。在限定的总重量(背包容量)内,如何选择物品放入背包,使得背包内物品的总价值最大,同时保证每种物品只能选择放入或不放入背包中一次,即不能选择物品的一部分。
                        • 问题描述

                          • 物品:有n种物品,每种物品的重量为w[i],价值为v[i],其中i为物品的编号,从1到n。
                          • 背包容量:背包的总容量为W。
                          • 目标:找出一种方案,使得在不超过背包容量W的前提下,放入背包的物品总价值最大。
                          • 解决方法

                            • 动态规划

                              • 动态规划是解决0-1背包问题的常用方法。其关键在于将问题分解为更小的子问题,并保存这些子问题的解,以避免重复计算。
                              • 定义状态:设f[i] [j]表示前i个物品中选取若干件放入容量为j的背包所能获得的最大价值。

                              • 状态转移方程

                                • 如果不放入第i个物品,则f[i][j] = f[i-1][j]。
                                • 如果放入第i个物品,则f[i][j] = f[i-1][j-w[i]] + v[i](前提是j >= w[i])。

                                  综合两种情况,可以得到状态转移方程:

                                  • f*[i][j]=max{f[i−1][j],if j public static void main(String[] args) { int[] weight = {10, 30, 20}; // 物品重量 int[] value = {60, 120, 100}; // 物品价值 int size = 50; // 背包容量 int[][] res = new int[weight.length + 1][(size / 10) + 1]; // 创建二维数组,0行0列用来存放初始值 String[][] resStr = new String[weight.length + 1][(size / 10) + 1]; // 用于存放路径 for (int i = 1; i for (int j = 1; j if (j
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]