java动态规划硬币划分,Java动态规划

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: " + getValue(values, rodLength));}}

输出结果:

Max rod value: 17

正如我们所看到的的,输出结果是一样的,所不同的是时间和空间复杂度。

通过从头开始解决子问题,我们消除了递归调用的需要,利用已解决给定问题的所有先前子问题的事实。

性能的提升

为了给出动态方法效率更高的观点的证据,让我们尝试使用30个值来运行该算法。一种算法需要大约5.2秒来执行,而动态解决方法需要大约0.000095秒来执行。

4. 简化的背包问题

简化的背包问题是一个优化问题,没有一个解决方案。这个问题的问题是 - “解决方案是否存在?”:

Given a set of items, each with a weight w1, w2... determine the number of each item to put in a knapsack so that the total weight is less than or equal to a given limit K.

给定一组物品,每个物品的重量为w1,w2 ......确定放入背包中的每个物品的数量,以使总重量小于或等于给定的极限K

首先让我们把元素的所有权重存储在W数组中。接下来,假设有n个项目,我们将使用从1到n的数字枚举它们,因此第i个项目的权重为W [i]。我们将形成(n + 1)x(K + 1)维的矩阵M。M [x] [y]对应于背包问题的解决方案,但仅包括起始数组的前x个项,并且最大容量为y

例如

假设我们有3个元素,权重分别是w1=2kg,w2=3kg,w3=4kg。利用上面的方法,我们可以说M [1] [2]是一个有效的解决方案。这意味着我们正在尝试用重量阵列中的第一个项目(w1)填充容量为2kg的背包。

在M [3] [5]中,我们尝试使用重量阵列的前3项(w1,w2,w3)填充容量为5kg的背包。这不是一个有效的解决方案,因为我们过度拟合它。

4.1. 矩阵初始化

当初始化矩阵的时候有两点需要注意:

Does a solution exist for the given subproblem (M[x][y].exists) AND does the given solution include the latest item added to the array (M[x][y].includes).

给定子问题是否存在解(M [x] [y] .exists)并且给定解包括添加到数组的最新项(M [x] [y] .includes)。

因此,初始化矩阵是相当容易的,M[0][k].exists总是false,如果k>0,因为我们没有把任何物品放在带有k容量的背包里。

另一方面,M[0][0].exists = true,当k=0的时候,背包应该是空的,因此我们在里面没有放任何东西,这个是一个有效的解决方案。

此外,我们可以说M[k][0].exists = true,但是对于每个k来说 M[k][0].includes = false。

注意:仅仅因为对于给定的M [x] [y]存在解决方案,它并不一定意味着该特定组合是解决方案。在M [10] [0]的情况下,存在一种解决方案 - 不包括10个元素中的任何一个。这就是M [10] [0] .exists = true但M [10] [0] .includes = false的原因。

4.2.算法原则

接下来,让我们使用以下伪代码构造M [i] [k]的递归关系:

if (M[i-1][k].exists == True):M[i][k].exists = TrueM[i][k].includes = Falseelif (k-W[i]>=0):if(M[i-1][k-W[i]].exists == true):M[i][k].exists = TrueM[i][k].includes = Trueelse:M[i][k].exists = False

因此,解决方案的要点是将子问题分为两种情况:

对于容量k,当存在第一个i-1元素的解决方案

对于容量k-W [i],当第一个i-1元素存在解决方案

第一种情况是不言自明的,我们已经有了问题的解决方案。

第二种情况是指了解第一个i-1元素的解决方案,但是容量只有一个第i个元素不满,这意味着我们可以添加一个第i个元素,并且我们有一个新的解决方案!

4.3. 实现

下面这何种实现方式,使得事情变得更加容易,我们创建了一个类Element来存储元素:

public class Element {private boolean exists;private boolean includes;public Element(boolean exists, boolean includes) {this.exists = exists;this.includes = includes;}

public Element(boolean exists) {this.exists = exists;this.includes = false;}

public boolean isExists {return exists;}

public void setExists(boolean exists) {this.exists = exists;}

public boolean isIncludes {return includes;}

public void setIncludes(boolean includes) {this.includes = includes;}}

接着,我们可以深入了解主要的类:

public class Knapsack {public static void main(String[] args) {Scanner scanner = new Scanner (System.in);System.out.println("Insert knapsack capacity:");int k = scanner.nextInt;

System.out.println("Insert number of items:");int n = scanner.nextInt;

System.out.println("Insert weights: ");int[] weights = new int[n + 1];

for (int i = 1; i <= n; i++) {weights[i] = scanner.nextInt;}

Element[][] elementMatrix = new Element[n + 1][k + 1];

elementMatrix[0][0] = new Element(true);

for (int i = 1; i <= k; i++) {elementMatrix[0][i] = new Element(false);}

for (int i = 1; i <= n; i++) {for (int j = 0; j <= k; j++) {elementMatrix[i][j] = new Element(false);if (elementMatrix[i - 1][j].isExists) {elementMatrix[i][j].setExists(true);elementMatrix[i][j].setIncludes(false);} else if (j >= weights[i]) {if (elementMatrix[i - 1][j - weights[i]].isExists) {elementMatrix[i][j].setExists(true);elementMatrix[i][j].setIncludes(true);}}}}

System.out.println(elementMatrix[n][k].isExists);}}

唯一剩下的就是解决方案的重建,在上面的类中,我们知道解决方案是存在的,但是我们不知道它是什么。

为了重建,我们使用下面的代码:

List solution = new ArrayList<>(n);if (elementMatrix[n][k].isExists) {int i = n;int j = k;while (j > 0 && i > 0) {if (elementMatrix[i][j].isIncludes) {solution.add(i);j = j - weights[i];}i = i - 1;}}

System.out.println("The elements with the following indexes are in the solution:n" + (solution.toString));

输出:

Insert knapsack capacity:12Insert number of items:5Insert weights:9 7 4 10 3trueThe elements with the following indexes are in the solution:[5, 1]

背包问题的一个简单变化是在没有价值优化的情况下填充背包,但现在每个单独项目的数量无限。

通过对现有代码进行简单调整,可以解决这种变化:

// Old code for simplified knapsack problemelse if (j >= weights[i]) {if (elementMatrix[i - 1][j - weights[i]].isExists) {elementMatrix[i][j].setExists(true);elementMatrix[i][j].setIncludes(true);}}// New code, note that we're searching for a solution in the same// row (i-th row), which means we're looking for a solution that// already has some number of i-th elements (including 0) in it's solutionelse if (j >= weights[i]) {if (elementMatrix[i][j - weights[i]].isExists) {elementMatrix[i][j].setExists(true);elementMatrix[i][j].setIncludes(true);}}

5. 传统的背包问题

利用以前的两种变体,现在让我们来看看传统的背包问题,看看它与简化版本的不同之处:

Given a set of items, each with a weight w1, w2... and a value v1, v2... determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit k and the total value is as large as possible.

在简化版中,每个解决方案都同样出色。但是,现在我们有一个找到最佳解决方案的标准(也就是可能的最大值)。请记住,这次我们每个项目都有无限数量,因此项目可以在解决方案中多次出现。

在实现中,我们将使用旧的类Element,其中添加了私有字段value,用于存储给定子问题的最大可能值:

public class Element {private boolean exists;private boolean includes;private int value;// appropriate constructors, getters and setters}

实现非常相似,唯一的区别是现在我们必须根据结果值选择最佳解决方案:

public static void main(String[] args) {// Same code as before with the addition of the values[] arraySystem.out.println("Insert values: ");int[] values = new int[n + 1];for (int i=1; i <= n; i++) {values[i] = scanner.nextInt;}

Element[][] elementMatrix = new Element[n + 1][k + 1];

// A matrix that indicates how many newest objects are used// in the optimal solution.// Example: contains[5][10] indicates how many objects with// the weight of W[5] are contained in the optimal solution// for a knapsack of capacity K=10int[][] contains = new int[n + 1][k + 1];

elementMatrix[0][0] = new Element(0);

for (int i = 1; i <= n; i++) {elementMatrix[i][0] = new Element(0);contains[i][0] = 0;}

for (int i = 1; i <= k; i++) {elementMatrix[0][i] = new Element(0);contains[0][i] = 0;}

for (int i = 1; i <= n; i++) {for (int j = 0; j <= k; j++) {elementMatrix[i][j] = new Element(elementMatrix[i - 1][j].getValue);contains[i][j] = 0;

elementMatrix[i][j].setIncludes(false);elementMatrix[i][j].setValue(M[i - 1][j].getValue);

if (j >= weights[i]) {if ((elementMatrix[i][j - weights[i]].getValue > 0 || j == weights[i])) {if (elementMatrix[i][j - weights[i]].getValue + values[i] > M[i][j].getValue) {elementMatrix[i][j].setIncludes(true);elementMatrix[i][j].setValue(M[i][j - weights[i]].getValue + values[i]);contains[i][j] = contains[i][j - weights[i]] + 1;}}}

System.out.print(elementMatrix[i][j].getValue + "/" + contains[i][j] + " ");}

System.out.println;}

System.out.println("Value: " + elementMatrix[n][k].getValue);}

输出:

Insert knapsack capacity:12Insert number of items:5Insert weights:9 7 4 10 3Insert values:1 2 3 4 50/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 1/1 0/0 0/0 0/00/0 0/0 0/0 0/0 0/0 0/0 0/0 2/1 0/0 1/0 0/0 0/0 0/00/0 0/0 0/0 0/0 3/1 0/0 0/0 2/0 6/2 1/0 0/0 5/1 9/30/0 0/0 0/0 0/0 3/0 0/0 0/0 2/0 6/0 1/0 4/1 5/0 9/00/0 0/0 0/0 5/1 3/0 0/0 10/2 8/1 6/0 15/3 13/2 11/1 20/4Value: 20

6. Levenshtein Distance

另一个使用动态规划的非常好的例子是Edit Distance或Levenshtein Distance。

Levenshtein Distance就是两个字符串A,B,我们需要使用原子操作将A转换为B:

字符串删除

字符串插入

字符替换(从技术上讲,它不止一个操作,但为了简单起见,我们称之为原子操作)

这个问题是通过有条理地解决起始字符串的子串的问题来处理的,逐渐增加子字符串的大小,直到它们等于起始字符串。

我们用于此问题的递归关系如下:

44540cd4721a4b1f11164d50bee445d9.png

如果a == b则c(a,b)为0,如果a = = b则c(a,b)为1。

实现:

public class editDistance {public static void main(String[] args) {String s1, s2;Scanner scanner = new Scanner(System.in);System.out.println("Insert first string:");s1 = scanner.next;System.out.println("Insert second string:");s2 = scanner.next;int n, m;n = s1.length;m = s2.length;

// Matrix of substring edit distances// example: distance[a][b] is the edit distance// of the first a letters of s1 and b letters of s2int[][] distance = new int[n + 1][m + 1];

// Matrix initialization:// If we want to turn any string into an empty string// the fastest way no doubt is to just delete// every letter individually.// The same principle applies if we have to turn an empty string// into a non empty string, we just add appropriate letters// until the strings are equal.for (int i = 0; i <= n; i++) {distance[i][0] = i;}for (int j = 0; j <= n; j++) {distance[0][j] = j;}

// Variables for storing potential values of current edit distanceint e1, e2, e3, min;

for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {e1 = distance[i - 1][j] + 1;e2 = distance[i][j - 1] + 1;if (s1.charAt(i - 1) == s2.charAt(j - 1)) {e3 = distance[i - 1][j - 1];} else {e3 = distance[i - 1][j - 1] + 1;}min = Math.min(e1, e2);min = Math.min(min, e3);distance[i][j] = min;}

}

System.out.println("Edit distance of s1 and s2 is: " + distance[n][m]);}}

输出:

Insert first string:manInsert second string:machineEdit distance of s1 and s2 is: 3

如果你想了解更多关于Levenshtein Distance的解决方案,我们在另外的一篇文章中用python实现了 Levenshtein Distance and Text Similarity in Python,使用这个逻辑,我们可以将许多字符串比较算法归结为简单的递归关系,它使用Levenshtein Distance的基本公式

7. 最长共同子序列(LCS)

这个问题描述如下:

Given two sequences, find the length of the longest subsequence present in both of them. A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.

给定两个序列,找到两个序列中存在的最长子序列的长度。子序列是以相同的相对顺序出现的序列,但不一定是连续的.

阐明:

如果我们有两个字符串s1="MICE"和s2="MINCE",最长的共同子序列是MI或者CE。但是,最长的公共子序列将是“MICE”,因为结果子序列的元素不必是连续的顺序。

递归关系与一般逻辑:

560a0bdcfb01a39cf87864e51dd6ac82.png

我们可以看到,Levenshtein distance和LCS之间只有微小的差别,特别是移动成本。

在LCS中,我们没有字符插入和字符删除的成本,这意味着我们只计算字符替换(对角线移动)的成本,如果两个当前字符串字符a [i]和b [j] 是相同的,则成本为1。

LCS的最终成本是2个字符串的最长子序列的长度,这正是我们所需要的。

Using this logic, we can boil down a lot of string comparison algorithms to simple recurrence relations which utilize the base formula of the Levenshtein distance

使用这个逻辑,我们可以将许多字符串比较算法归结为简单的递归关系,它使用Levenshtein distance的基本公式。

实现:

public class LCS {public static void main(String[] args) {String s1 = new String("Hillfinger");String s2 = new String("Hilfiger");int n = s1.length;int m = s2.length;int[][] solutionMatrix = new int[n+1][m+1];for (int i = 0; i < n; i++) {solutionMatrix[i][0] = 0;}for (int i = 0; i < m; i++) {solutionMatrix[0][i] = 0;}for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {int max1, max2, max3;max1 = solutionMatrix[i - 1][j];max2 = solutionMatrix[i][j - 1];if (s1.charAt(i - 1) == s2.charAt(j - 1)) {max3 = solutionMatrix[i - 1][j - 1] + 1;} else {max3 = solutionMatrix[i - 1][j - 1];}int tmp = Math.max(max1, max2);solutionMatrix[i][j] = Math.max(tmp, max3);}}

System.out.println("Length of longest continuous subsequence: " + solutionMatrix[n][m]);}}

输出:

Length of longest continuous subsequence: 8

8.利用动态规划的其他问题

利用动态规划可以解决很多问题,下面列举了一些:

分区问题:给定一组整数,找出它是否可以分成两个具有相等和的子集

子集和问题:给你一个正整数的数组及元素还有一个合计值,是否在数组中存在一个子集的的元素之和等于合计值。

硬币变化问题:鉴于给定面额的硬币无限供应,找到获得所需变化的不同方式的总数

k变量线性方程的所有可能的解:给定k个变量的线性方程,计算它的可能解的总数

找到醉汉不会从悬崖上掉下来的概率:给定一个线性空间代表距离悬崖的距离,让你知道酒鬼从悬崖起始的距离,以及他向悬崖p前进并远离悬崖1-p的倾向,计算出他的生存概率9.结论

动态编程是一种工具,可以节省大量的计算时间,以换取更大的空间复杂性,这在很大程度上取决于您正在处理的系统类型,如果CPU时间很宝贵,您选择耗费内存的解决方案,另一方面,如果您的内存有限,则选择更耗时的解决方案。

原文:https://stackabuse.com/dynamic-programming-in-java/

作者:Vladimir Batoćanin

译者:lee

解耦Java模块的设计策略

上篇好文:

使用 Spring Data JPA 进行分页和排序

文章对你是否有帮助呢?

别忘记点右上角按钮分享给更多人哦~

点击在看,和我一起帮助更多开发者!返回搜狐,查看更多

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值