算法设计--动态规划

1.简介

        动态规划(Dynamic Programming)是一种解决复杂问题的算法思想。它通过将问题分解为一系列子问题,并将子问题的解存储起来,从而避免重复计算,提高算法效率。

Dynamic:在这里指用数学方法来根据子问题求解当前问题(通俗理解就是找到递推公式)

Programming:指缓存上一步结果,根据上一步结果计算当前结果(多阶段进行)

动态规划的要点包括:

  1. 最优子结构:问题的最优解包含子问题的最优解。
  2. 无后效性:一个阶段的状态只受前面若干个阶段的状态影响,与未来阶段无关。
  3. 状态转移方程:用数学表达式描述问题状态之间的关系,建立状态转移方程是动态规划的核心。
  4. 即找到子问题的解,推导出当前问题的解,推导过程可以表达为一个数学公式,用一维或二维数组来保存之前的计算结果(可以进一步优化)

动态规划能够解决一些具有重叠子问题和最优子结构特点的问题,例如:

  1. 路径规划问题:最短路径、最优路径等。
  2. 背包问题:在给定的容量下选择最有价值的物品组合。
  3. 字符串处理问题:编辑距离、最长公共子序列等。
  4. 图论问题:最小生成树、最短路径等。

总之,动态规划是一种有效的算法思想,可以解决许多优化问题,提高计算效率。

2.降维处理: 

         二维数组降为一维数组,一维数组降低为几个变量

3.斐波那契

递推公式

    public static int fibonacci1(int n) {
        int[] dp = new int[n + 1];//用来缓存结果
        if (n == 0) {
            dp[0] = 0;
            return 0;
        }
        if (n == 1) {
            dp[1] = 1;
            return 1;
        }
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
    //降维优化:
    public static int fibonacci(int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }
        int a = 0;//前两项
        int b = 1;//前一项
        int c = 0;
        for (int i = 2; i <= n; i++) {
            c = a + b;//当前项
            //对a,b进行更新
            a = b;
            b = c;
        }
        return c;//最后一次循环,b就是要求的c
    }

4.BellmanFord-求最短路径问题 

 

public class BellmanFord {

    static class Edge {
        int from;
        int to;
        int weight;

        public Edge(int from, int to, int weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }
    }

    /**
     * f(v) 用来表示从起点出发,到达 v 这个顶点的最短距离
     * 初始时
     * f(v) = 0     当 v == 起点 时
     * f(v) = ∞     当 v != 起点 时
     * <p>
     * 之后
     * 新        旧       所有from
     * f(to) = min(f(to),  f(from)  +  from.weight)
     * <p>
     * v1       v2      v3     v4      v5      v6
     * 0        ∞       ∞       ∞       ∞       ∞
     * <p>
     * from 从哪儿来
     * to   到哪儿去
     * <p>
     * f(v4) = min( ∞ , f(v3) + 11) = 20
     * f(v4) = min( 20 , f(v2) + 15) = 20
     * <p>
     * 找出递归公式,将当前问题分解成子问题,分阶段进行求解,求解过程中缓存子问题的解,避免重复计算
     * 进行的轮数==顶点数-1
     * <p>
     * v1       v2      v3     v4      v5      v6
     * 0        ∞       ∞       ∞       ∞       ∞
     * 0       7       9       ∞       ∞       14      第一轮
     * 0       7       9      20       23      11      第二轮
     * 0       7       9      20       20      11      第三轮
     * 0       7       9      20       20      11      第四轮
     * 0       7       9      20       20      11      第五轮
     */

    public static void main(String[] args) {
        List<Edge> edges = List.of(
                new Edge(6, 5, 9),
                new Edge(4, 5, 6),
                new Edge(1, 6, 14),
                new Edge(3, 6, 2),
                new Edge(3, 4, 11),
                new Edge(2, 4, 15),
                new Edge(1, 3, 9),
                new Edge(1, 2, 7)
        );

        //一维数组缓存子问题的解,数组里的元素代表每个元素的最短距离【从v1出发】
        int[] dp = new int[7];//一位数组,用来缓存结果
        dp[1] = 0;
        for (int i = 2; i < 7; i++) {
            dp[i] = Integer.MAX_VALUE;
        }
        进行多轮这样的操作,才能达到我们想要的结果,进行的轮数是顶点数-1
        for (int i = 0; i < 5; i++) {
            for (Edge e : edges) {
                if (dp[e.from] != Integer.MAX_VALUE) {//v1是已知的,所以最先处理和v1相邻的
                    dp[e.to] = Integer.min(dp[e.to], dp[e.from] + e.weight);
                }
            }
        }

    }

    static void print(int[] dp) {
        System.out.println(Arrays.stream(dp)
                .mapToObj(i -> i == Integer.MAX_VALUE ? "∞" : String.valueOf(i)).
                collect(Collectors.joining(",", "[", "]")));
    }

}

5.Leetcode62--不同路径

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];//存储对应坐标点处的路径总数
        for (int i = 0; i < m; i++) {
            dp[i][0] = 1;
        }
        for (int j = 0; j < n; j++) {
            dp[0][j] = 1;
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                //          上面              左面
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
}

 降维处理:

 public int uniquePaths(int m, int n) {
        int[] dp = new int[n];//存储对应坐标点处的路径总数
        for (int i = 0; i < n; i++) {//给第一,每一列初始化为1
            dp[i] = 1;
        }
        for (int i = 1; i < m; i++) {
            dp[0] = 1;//j=0这一列的值一直为1
            for (int j = 1; j < n; j++) {
                dp[j] = dp[j - 1] + dp[j];
            }
        }
        return dp[n - 1];
    }

6.01背包问题

贪心算法(效率较高)求解是近似的最优解,不是全局最优解,现在用动态规划重新求解最优解

二维表格:

行:要装入的物品

列:对应背包的最大容量

装不下保留上次的价值不变,能装下时,进行比较,然后替换

public class DPKnapsackProblem {
         /*
        1. n个物品都是固体,有重量和价值
        2. 现在你要取走不超过 10克 的物品
        3. 每次可以不拿或全拿,问最高价值是多少

            编号 重量(g)  价值(元)
            0   1       1_000_000      钻石一粒         选中  D
            1   4       1600           黄金一块   400   选中  A
            2   8       2400           红宝石一粒 300        R
            3   5       30             白银一块         选中  S
        1_001_630

        1_002_400

        0   1   2   3   4   5   6   7   8   9   10背包容量
      0 0   0   0   0   A   A   A   A   A   A   A   黄金
      1 0   0   0   0   A   A   A   A   R   R   R   红宝石
      2 0   0   0   0   A   A   A   A   R   R   R   白银
      3 0   D   D   D   D  D+A D+A D+A D+A  D+R D+R 钻石

      if(装不下){
            dp[i][j] = dp[i-1][j]
      }else{    装得下
            dp[i][j] = max(dp[i-1][j], item.value + dp[i-1][j-item. Weight])
      }
     */


    static class Item {
        int index;
        String name;
        int weight;//总重
        int value;//总结之

        public Item(int index, String name, int weight, int value) {
            this.index = index;
            this.name = name;
            this.weight = weight;
            this.value = value;
        }

        public int unitValue() {
            return value / weight;
        }

        @Override
        public String toString() {
            return "Item(" + index + ")";
        }
    }

    public static void main(String[] args) {
        Item[] items = new Item[]{
                new Item(1, "黄金", 4, 1600),
                new Item(2, "宝石", 8, 2400),
                new Item(3, "白银", 1, 30),
                new Item(4, "钻石", 1, 10_000)
        };
        System.out.println(select(items, 10));
    }

    /**
     * @param items
     * @param total
     * @return 选中物品的最大价值
     */
    static int select1(Item[] items, int total) {
        int[][] dp = new int[items.length][total + 1];
        Item item0 = items[0];
        for (int j = 0; j < total + 1; j++) {//第一行单独处理
            if (j >= item0.weight) {//装得下
                dp[0][j] = item0.value;
            } else {                      //装不下
                dp[0][j] = 0;
            }
        }

        for (int i = 1; i < dp.length; i++) {
            Item item = items[i];
            for (int j = 0; j < total + 1; j++) {
                if (j >= item.weight) {//装得下
                    //每个物品只有1件,所以要到上一行去找
                    dp[i][j] = Integer.max(dp[i - 1][j], item.value + dp[i - 1][j - item.weight]);
                } else {//装不下
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[items.length - 1][total];
    }


    //降维处理
    static int select(Item[] items, int total) {
        int[] dp = new int[total + 1];
        Item item0 = items[0];
        for (int j = 0; j < total + 1; j++) {//第一行单独处理
            if (j >= item0.weight) {//装得下
                dp[j] = item0.value;
            } else {                      //装不下
                dp[j] = 0;
            }
        }
        //降维之后,处理的顺序不能从左向右,要改为从右向左
        for (int i = 1; i < items. Length; i++) {
            Item item = items[i];
            for (int j = total; j > 0; j--) {
                if (j >= item.weight) {//装得下
                    dp[j] = Integer.max(dp[j], item.value + dp[j - item.weight]);
                }
            }
        }
        return dp[total];
    }
}

7.完全背包问题  

        每件物品有无限多件

        行:要装入的物品

        列:对应背包的最大容量

        装不下保留上次的价值不变,能装下时,进行比较,然后替换

public class KnapsackProblemComplete {

    /**
     * 0   1   2   3   4   5   6
     * 1 0   0   c   c   cc  cc  ccc
     * 2 0   0   c   s   cc  sc  ccc
     * 3 0   0   c   s   a   a   ac
     * <p>
     * <p>
     * <p>
     * if(装不下){
     * dp[i][j] = dp[i-1][j]
     * }else{    装得下                                因为每件商品有无限件,所以不用去上一行找,在当前行找就行了
     * dp[i][j] = max(dp[i-1][j], item.value + dp[i][j-item.weight])
     * }
     */

    static class Item {
        int index;
        String name;
        int weight;//总重
        int value;//总结之

        public Item(int index, String name, int weight, int value) {
            this.index = index;
            this.name = name;
            this.weight = weight;
            this.value = value;
        }

        public int unitValue() {
            return value / weight;
        }

        @Override
        public String toString() {
            return "Item(" + index + ")";
        }
    }

    public static void main(String[] args) {
        Item[] items = new Item[]{
                new Item(1, "青铜", 2, 3),//c
                new Item(2, "白银", 3, 4),//s
                new Item(3, "黄金", 4, 7)//a
        };
        System.out.println(select(items, 6));
    }

    private static int select1(Item[] items, int total) {
        int[][] dp = new int[items.length][total + 1];
        Item item0 = items[0];
        for (int j = 0; j < total + 1; j++) {//第一行单独处理
            if (j >= item0.weight) {//装得下
                dp[0][j] = item0.value + dp[0][j - item0.weight];
            }
        }

        for (int i = 1; i < items.length; i++) {
            Item item = items[i];
            for (int j = 0; j < total + 1; j++) {
                if (j >= item.weight) {
                    //                      上次的最大价值     当前物品价值  剩余空间能装的最大价值
                    dp[i][j] = Integer.max(dp[i - 1][j], item.value + dp[i][j - item.weight]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[items.length - 1][total];
    }

    //降维处理
    private static int select(Item[] items, int total) {
        int[] dp = new int[total + 1];
        for (int i = 0; i < items.length; i++) {
            Item item = items[i];
            for (int j = 0; j < total + 1; j++) {
                if (j >= item.weight) {
                    //                      上次的最大价值     当前物品价值  剩余空间能装的最大价值
                    dp[j] = Integer.max(dp[j], item.value + dp[j - item.weight]);
                }
            }
        }
        return dp[total];
    }
}

两种背包问题的区别:

7.零钱兑换问题

Leetcode322

和完全背包问题非常类似

不同面值(有无限个)的硬币,凑成给定的金额,求用的最少硬币数

/**
 * 零钱兑换问题【拿不同面值硬币凑总金额,硬币数量不限】,和完全背包问题特别像【拿不同的物品装入背包,物品的数目不限】
 * 不同点:最少硬币个数           拿到的最大价值
 */
public class ChangeMakingProblemLeetcode322 {

    /**
     * * 0   1   2   3   4   5   6
     * * 1 0   0   c   c   cc  cc  ccc
     * * 2 0   0   c   s   cc  sc  ccc
     * * 3 0   0   c   s   a   a   ac
     */

    public int coinChange1(int[] coins, int amount) {
        int[][] dp = new int[coins.length][amount + 1];
        //对第一个硬币特殊处理,因为他没有上一个硬币
        //装不下的时候,初始价值改为max,比amount大1即可
        for (int j = 1; j < amount + 1; j++) {
            if (j >= coins[0]) {//放得下
                dp[0][j] = dp[0][j - coins[0]] + 1;
            } else {//放不下
                dp[0][j] = amount + 1;//最大值
            }
        }
        for (int i = 1; i < coins.length; i++) {
            for (int j = 1; j < amount + 1; j++) {//从容量为1开始尝试
                if (j >= coins[i]) {
                    //                                剩余空间能放的最少硬币数+当前硬币数1
                    dp[i][j] = Integer.min(dp[i - 1][j], dp[i][j - coins[i]] + 1);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[coins.length - 1][amount] < amount + 1 ? dp[coins.length - 1][amount] : -1;
    }

    //降维处理

    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        //对第一个硬币特殊处理,因为他没有上一个硬币
        //装不下的时候,初始价值改为max
        //把处理第一个硬币和后面的硬币合在一起,因为毕竟比较相似,所以先把一维数组初始化
        //0 max max max max max
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;
        for (int coin : coins) {
            for (int j = coin; j < amount + 1; j++) {//放不下的条件直接跳过
                dp[j] = Integer.min(dp[j], dp[j - coin] + 1);
            }
        }
        return dp[amount] < amount + 1 ? dp[amount] : -1;
    }
}

8.零钱兑换||

 Leetcode518

凑成总金额有几种凑法

/**
 * 凑成总金额有几种凑法
 */
public class ChangeMakingProblemLeetcode518 {
    public int change1(int amount, int[] coins) {
        int[][] dp = new int[coins.length][amount + 1];
        //第一个硬币比较特殊,单独处理
        for (int i = 0; i < coins.length; i++) {
            dp[i][0] = 1;
        }
        for (int j = 1; j < amount + 1; j++) {
            if (j >= coins[0]) {
                dp[0][j] = dp[0][j - coins[0]];
            }
        }
        for (int i = 1; i < coins.length; i++) {
            for (int j = 1; j < amount + 1; j++) {
                if (j >= coins[i]) {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[coins.length - 1][amount];
    }

    //降维处理
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        //第一个硬币比较特殊,单独处理
        dp[0]=1;
        for (int coin : coins) {
            for (int j = coin; j < amount + 1; j++) {
                dp[j] = dp[j] + dp[j - coin];
            }
        }
        return dp[amount];
    }
}

 9.钢条切割问题

        题目描述:不同长度的钢条有各自的价值,给一条固定长度的钢条,怎么切割,价值最大

和完全背包问题非常像

钢条总长度==背包容量

某段钢条的长度==物品重量

列为总长度,行为对应长度的钢条

/**
 * 钢条切割问题:
 * 不同长的的钢条有不同的价值,问现有一钢条,怎么切割价值最大
 */
public class CutRodProblem {
    /**
     * @param values 物品的数组:数组的索引对应钢条的长度(物品重量)。数组的值对应不同长度的价值
     * @param n      要切割的钢条总长度
     * @return
     */
    static int count(int[] values, int n) {
        int[][] dp = new int[values.length][n + 1];
        for (int i = 1; i < values.length; i++) {//重量为0的切法也不考虑
            for (int j = 1; j < n + 1; j++) {
                if (j >= i) {
                    dp[i][j] = Integer.max(dp[i - 1][j], values[i] + dp[i][j - i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[values.length - 1][n];
    }

    public static void main(String[] args) {
        //不同长度钢条的价值数组,数组的索引对应钢条的长度(物品重量)
        System.out.println(count(new int[]{0, 1, 5, 8, 9}, 4));
    }
}

10.最长公共子串

例如:"itheima", "thema" 的最长公共子串是the 不是ma

子串要求在原始字符串中连续

 下述代码可以用字符串转为数组,string.toCharArray()方法,效率更好

public class LCSubstring {
    static int lcs(String a, String b) {
        int[][] dp = new int[b.length()][a.length()];
        int max = 0;
        for (int i = 0; i < b.length(); i++) {
            for (int j = 0; j < a.length(); j++) {
                if (a.charAt(j) == b.charAt(i)) {
                    if (i == 0 || j == 0) {//说明没有上一行
                        dp[i][j] = 1;
                    } else {
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                    max = Integer.max(max, dp[i][j]);
                } else {
                    dp[i][j] = 0;
                }
            }
        }
        return max;
    }

    public static void main(String[] args) {
        System.out.println(lcs("itheima", "thema"));
    }
}

 11.最长公共子序列

Leetcode1143

子序列:可以在原来的字符串中不连续,但是顺序不能颠倒

public class Leetcode1143 {
    public int longestCommonSubsequence1(String text1, String text2) {
        //二维数组额外+1行,1列,为了处理i-1的问题,少考虑边界条件
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i < m + 1; i++) {
            for (int j = 1; j < n + 1; j++) {
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Integer.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }

    //降维处理
    public int longestCommonSubsequence(String text1, String text2) {
        //二维数组额外+1行,1列,为了处理i-1的问题,少考虑边界条件
        int m = text1.length();
        int n = text2.length();
        char[] chars1 = text1.toCharArray();
        char[] chars2 = text2.toCharArray();
        int[] dp = new int[n + 1];
        for (int i = 1; i < m + 1; i++) {
            int prev = 0;// 上一行的左上角元素值(dp[j-1]),变成一维数组要单独处理
            char x = chars1[i - 1];
            for (int j = 1; j < n + 1; j++) {
                char y = chars2[j - 1];
                int temp = dp[j]; // 临时保存当前行当前列的值
                if (x == y) {
                    dp[j] = prev + 1; // 当前行当前列的值更新为上一行左上角元素值+1
                } else {
                    dp[j] = Integer.max(dp[j], dp[j - 1]);
                }
                prev = temp;
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        int count = new Leetcode1143().longestCommonSubsequence("abcba", "abcbcba");
        System.out.println(count);
    }
}

 11.两个字符串删除

Leetcode583

核心:找到公共子序列

public class Leetcode1143 {
    public int longestCommonSubsequence1(String text1, String text2) {
        //二维数组额外+1行,1列,为了处理i-1的问题,少考虑边界条件
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i < m + 1; i++) {
            for (int j = 1; j < n + 1; j++) {
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Integer.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }

    //降维处理
    public int longestCommonSubsequence(String text1, String text2) {
        //二维数组额外+1行,1列,为了处理i-1的问题,少考虑边界条件
        int m = text1.length();
        int n = text2.length();
        char[] chars1 = text1.toCharArray();
        char[] chars2 = text2.toCharArray();
        int[] dp = new int[n + 1];
        for (int i = 1; i < m + 1; i++) {
            int prev = 0;// 上一行的左上角元素值(dp[j-1]),变成一维数组要单独处理
            char x = chars1[i - 1];
            for (int j = 1; j < n + 1; j++) {
                char y = chars2[j - 1];
                int temp = dp[j]; // 临时保存当前行当前列的值
                if (x == y) {
                    dp[j] = prev + 1; // 当前行当前列的值更新为上一行左上角元素值+1
                } else {
                    dp[j] = Integer.max(dp[j], dp[j - 1]);
                }
                prev = temp;
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        int count = new Leetcode1143().longestCommonSubsequence("abcba", "abcbcba");
        System.out.println(count);
    }
}

12.最长递增子序列

    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1);

        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                //nums[i] 外层循环正在处理的元素   nums[j]前面的元素
                if (nums[i] > nums[j]) {//满足了升序条件
                    dp[i] = Integer.max(dp[i], dp[j] + 1);
                }
            }
        }
        return Arrays.stream(dp).max().getAsInt();
    }

13.卡特兰数

n个节点所能组成的二叉搜索树的总和称为卡特兰数

二叉搜索树:左边的比它小,右面的比它大 

为了避免重复的计算,较好的办法,采用动态规划,一维数组来记录

    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        //把所有的卡特兰数计算出来,填充到dp数组里
        for (int j = 2; j < n + 1; j++) {
            for (int i = 0; i < j; i++) {//计算第j个卡特兰数
                dp[j] += dp[i] * dp[j - 1 - i];
            }
        }
        return dp[n];
    }

 应用:

1.Leetcode96--不同的二叉搜索树 

2.n个元素进栈序列为:1,2,3,4,... n则有多少种出栈顺序

3.Leetcode22--括号生成

类似卡特兰数:不同之处。把每种不同的组合要记录下来

把int数组改为ArrayList数组,里面存放字符串

性能不是很好,4层for循环套起来,性能就下去了

    public List<String> generateParenthesis(int n) {
        ArrayList<String>[] dp = new ArrayList[n + 1];
        dp[0] = new ArrayList<>(List.of(""));
        dp[1] = new ArrayList<>(List.of("()"));
        for (int j = 2; j < n + 1; j++) {
            dp[j] = new ArrayList<>();//准备初始的空的集合,数组是引用类型,初始的值都是null
            for (int i = 0; i < j; i++) {
                for (String k1 : dp[i]) {
                    for (String k2 : dp[j - i - 1]) {
                    //i对应的集合是内层要嵌套的括号,j-i-1对应的集合是平级要拼接的括号
            //k1是内层要嵌套的括号,外层有一对括号把k1包围起来,k2是平级要拼接的,拼起来即可
                        dp[j].add("(" + k1 + ")" + k2);
                    }
                }
            }
        }
        return dp[n];
    }

14.打家劫舍 

Leetcode198

和01背包问题有类似之处

定义一维数组:数组长度和房间保持一致,数组中的每个元素表示偷到第几个房间的最大价值

    public int rob(int[] nums) {
        int length = nums.length;
        if (length == 1) {
            return nums[0];
        }
        int[] dp = new int[length];
        dp[0] = nums[0];
        dp[1] = Integer.max(nums[0], nums[1]);
        for (int i = 2; i < length; i++) {
            dp[i] = Integer.max(nums[i] + dp[i - 2], dp[i - 1]);
        }
        return dp[length - 1];
    }

14.旅行商问题 

        现有n个城市,城市与城市之间具有一定的花费,假设你是一个旅行商人,要去这n个城市售卖你的商品,要求:不管从哪个城市出发,这n个城市必须都经历一边,问哪条路线花费最少

不能和最短路径混淆,因为最短路径不经过每个城市

争对该问题没有特别好的解决办法,在数据量较少时,可以得到最优解【利用动态规划】,如果数据量比较大,那么得不到最优解,只有近似解

 用邻接矩阵表示图(无向图)

行还是表示城市的数量,但是列,列想表示一个集合,如何表示?

答:用二进制数代表集合中是否有这个城市 

最终结果在右上角

/**
 * 旅行商问题:
 * 不管从哪个城市出发,每个城市必须都经历一边【必须回到起点】,
 * 哪条路线花费最好【和最短路径问题不一样,最短路径问题不需要经过每个城市】
 */
public class TravellingSalesmanProblem {

    public static void main(String[] args) {
        int[][] graph = {
                {0, 1, 2, 3},
                {1, 0, 6, 4},
                {2, 6, 0, 5},
                {3, 4, 5, 0}
        };
    }

    /**
     * 推到是从右向左进行的,但是填表不行,需要用到左侧的数值,填表从左向右来填
     *
     * @param graph 邻接矩阵【表示顶点之间相邻关系的矩阵】,存放城市与城市之间的距离【成本】
     * @return
     */
    static int tsp(int[][] graph) {
        int m = graph.length;//城市数目
        int n = 1 << (m - 1);//剩余城市的组合数 2^(m-1)
        int[][] dp = new int[m][n];

        //填充第0列
        for (int k = 0; k < m; k++) {
            dp[k][0] = graph[k][0];
        }

        //填充后续列
        for (int j = 1; j < n; j++) {
            //循环每一行
            for (int i = 0; i < m; i++) {
                dp[i][j] = Integer.MAX_VALUE / 2;//初始化数值
                if (contains(j, i)) {//剩余城市集合已包含出发城市,不合理
                    continue;
                }
                //填充单元格
                for (int k = 0; k < m; k++) {
                    if (contains(j, k)) {
                        dp[i][j] = Integer.min(dp[i][j], graph[i][k] + dp[k][exclude(j, k)]);
                    }
                }
            }
        }
        return dp[0][n - 1];
    }

    static boolean contains(int set, int city) {
        return (set >> (city - 1) & 1) == 1;//set右移
    }

    /**
     * 集合set里 去除city
     *
     * @param set
     * @param city
     * @return 剩下的城市
     */
    static int exclude(int set, int city) {
        return set ^ (1 << (city - 1));//^     相同为0,不同为1    1左移
    }

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray-国

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值