Java动态规划

本文深入探讨了Java中的动态规划概念,通过介绍斐波那契数列、背包问题、Levenshtein Distance和最长共同子序列(LCS)等案例,展示了动态规划如何解决复杂问题,提高算法效率。动态规划通过将问题分解为子问题,避免了递归中的冗余计算,提高了程序性能。
摘要由CSDN通过智能技术生成

1. 介绍

动态规划典型的被用于优化递归算法,因为它们倾向于以指数的方式进行扩展。动态规划主要思想是将复杂问题(带有许多递归调用)分解为更小的子问题,然后将它们保存到内存中,这样我们就不必在每次使用它们时重新计算它们。

要理解动态规划的概念,我们需要熟悉一些主题:

  1. 什么是动态规划?
  2. 贪心算法
  3. 简化的背包问题
  4. 传统的背包问题
  5. Levenshtein Distance
  6. LCS-最长的共同子序列
  7. 利用动态规划的其他问题
  8. 结论

本文所有代码均为java代码实现。

(想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

2. 什么是动态规划?

动态规划是一种编程原理,可以通过将非常复杂的问题划分为更小的子问题来解决。这个原则与递归很类似,但是与递归有一个关键点的不同,就是每个不同的子问题只能被解决一次。

为了理解动态规划,我们首先需要理解递归关系的问题。每个单独的复杂问题可以被划分为很小的子问题,这表示我们可以在这些问题之间构造一个递归关系。 让我们来看一个我们所熟悉的例子:斐波拉契数列,斐波拉契数列的定义具有以下的递归关系:

在这里插入图片描述
注意:递归关系是递归地定义下一项是先前项的函数的序列的等式。Fibonacci序列就是一个很好的例子。
所以,如果我们想要找到斐波拉契数列序列中的第n个数,我们必须知道序列中第n个前面的两个数字。

但是,每次我们想要计算Fibonacci序列的不同元素时,我们在递归调用中都有一些重复调用,如下图所示,我们计算Fibonacci(5)

在这里插入图片描述
例如:如果我们想计算F(5),明显的我们需要计算F(3)F(4)作为计算F(5)的先决条件。然而,为了计算F(4),我们需要计算F(3)F(2),因此我们又需要计算F(2)F(1)来得到F(3),其他的求解诸如此类。

这样的话就会导致很多重复的计算,这些重复计算本质上是冗余的,并且明显的减慢了算法的效率。为了解决这种问题,我们介绍动态规划。

在这种方法中,我们对解决方案进行建模,就像我们要递归地解决它一样,但我们从头开始解决它,记忆到达顶部采取的子问题(子步骤)的解决方案。 因此,对于Fibonacci序列,我们首先求解并记忆F(1)F(2),然后使用两个记忆步骤计算F(3),依此类推。这意味着序列中每个单独元素的计算都是O(1),因为我们已经知道前两个元素。

当使用动态规划解决问题的时候,我们一般会采用下面三个步骤:

  1. 确定适用于所述问题的递归关系
  2. 初始化内存、数组、矩阵的初始值
  3. 确保当我们进行递归调用(可以访问子问题的答案)的时候它总是被提前解决。

遵循这些规则,让我们来看一下使用动态规划的算法的例子:

3. 贪心算法

下面来以这个为例子:

Given a rod of length n and an array that contains prices of all pieces of size smaller than n. Determine the maximum value obtainable by cutting up the rod and selling the pieces.
3.1. 对于没有经验的开发者可能会采取下面这种做法

这个问题实际上是为动态规划量身定做的,但是因为这是我们的第一个真实例子,让我们看看运行这些代码会遇到多少问题:

public class naiveSolution {  
    static int getValue(int[] values, int length) {
        if (length <= 0)
            return 0;
        int tmpMax = -1;
        for (int i = 0; i < length; i++) {
            tmpMax = Math.max(tmpMax, values[i] + getValue(values, length - i - 1));
        }
        return tmpMax;
    }

    public static void main(String[] args) {
        int[] values = new int[]{3, 7, 1, 3, 9};
        int rodLength = values.length;

        System.out.println("Max rod value: " + getValue(values, rodLength));
    }
}

输出结果:

Max rod value: 17

该解决方案虽然正确,但效率非常低,递归调用的结果没有保存,所以每次有重叠解决方案时,糟糕的代码不得不去解决相同的子问题。

3.2.动态方法

利用上面相同的基本原理,添加记忆化并排除递归调用,我们得到以下实现:

public class dpSolution {  
    static int getValue(int[] values, int rodLength) {
        int[] subSolutions = new int[rodLength + 1];

        for (int i = 1; i <= rodLength; i++) {
            int tmpMax = -1;
            for (int j = 0; j < i; j++)
                tmpMax = Math.max(tmpMax, values[j] + subSolutions[i - j - 1]);
            subSolutions[i] = tmpMax;
        }
        return subSolutions[rodLength];
    }

    public static void main(String[] args) {
        int[] values = new int[]{3, 7, 1, 3, 9};
        int rodLength = values.length;

        System.out.println("Max rod value: " + getV
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值