赞
踩
(1)重复子问题
(2)最优子结构
动态规划算法与分治算法的异同点:
(1)动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题
(2)分治算法经分解得到的子问题往往是独立的
(3)动态规划算法经分解得到的子问题往往不是独立的,有些子问题被重复计算多次
动态规划求解的基本步骤:
(1)找出最优解的性质,并刻划其结构特征
(2)递归地定义最优值
(3)以自底向上的方式计算出最优值
(4)根据计算最优值时得到的信息,构造最优解
有一只小兔子站在一片三角形的胡萝卜地的入口,如右图所示,图中的数字表示每一个坑中胡萝卜的数量,小兔子每次只能跳到左下角或者右下角的坑中,请问小兔子怎么跳才能得到最多数量的胡萝卜?
如果我们采用暴力法来解决这个问题,很明显从第一层开始,每次都有两个选择,左下角和右下角,也就是n层的话,有 2 n − 1 2^{n-1} 2n−1 条;路径,T(n) = 2 n 2^n 2n。
大致代码实现思路:
a [i][j]
开始,最多的胡萝卜数是不是为a[i][j]
加上以右下角为起点和左下角为起点路径中的胡萝卜最大数的路径,循坏递归,因为每个点都有两条路径选择,每次选择路径中胡萝卜最多的路径。i = n+1
时结束,因为我们有n层,到n+1层的时候自然就结束。实现代码:
public class Demo {
public static void main(String[] args) {
int[][] a = {{1},{3,2},{4,10,1},{4,3,2,20}};
System.out.println(solve(a,0,0));
}
public static int solve(int[][] a,int i,int j){
//第 n+1 层结束 ===》从0层开始计算 ,那么 i = n 时结束
if (i == a.length){
return 0;
}
return a[i][j]+ Math.max(solve(a,i+1,j),solve(a,i+1,j+1));
}
}
详情见文章:【算法】备忘录法(记忆化搜索)
上面递归时候,我们solve(a,2,1)
被重复计算过两次,随着层数的增加,我们重复计算的子问题也会增加,为了避免重复计算子问题,我们就需要用到备忘录法,就是利用一个二维数组记录每次子问题计算的值,每次需要计算子问题时,先判断数组中是否计算过保存了,有的话直接用数组中结果,没有就计算并把结果保存到数组中。
代码实现:
public class Demo {
public static void main(String[] args) {
int[][] a = {{1},{3,2},{4,10,1},{4,3,2,20}};
System.out.println(solve(a,0,0,new int[a.length][a.length]));
}
public static int solve(int[][] a,int i,int j,int[][] p){
//第 n+1 层结束 ===》从0层开始计算 ,那么 i = n 时结束
if (i == a.length){
return 0;
}
if (p[i][j] == 0) {
p[i][j] = a[i][j] + Math.max(solve(a, i + 1, j, p), solve(a, i + 1, j + 1, p));
}
return p[i][j];
}
}
p[i][j]
表示(i, j)的达到最后一层的最大路径和,那么p[i][j]的最优解包含了子问题p[i+1][j]
或p[i+1][j+1]
的最优解
状态转移方程(递归方程):
图解:
我们最终结果是p[0][0]
动态规划法又叫填表法,填完上面那张表我们的结果就出来了
实现代码:
public class Demo {
public static void main(String[] args) {
int[][] a = {{1},{3,2},{4,10,1},{4,3,2,20}};
System.out.println(solve(a));
}
public static int solve(int[][] a){
int[][] p = a.clone();
//最后一层的数不需要修改 ,从倒数第二次开始
for (int i = a.length -2; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
p[i][j] = a[i][j] + Math.max(p[i+1][j],p[i+1][j+1]);
}
}
return p[0][0];
}
}
p[i][j]
表示从(1,1)到达(i, j) 的最大路径和,那么p[i][j]
的最优解包含了子问题p[i-1][j-1]
或p[i-1][j]
的最优解
状态转移方程(递归方程):
思路一就是从表的最后一层开始填,思路二是表的第一层开始填:
LCS,Longest Common Subsequence
(1) 串中任意个连续的字符组成的子序列称为该串的子串。
(2) 某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。
公共子序列
给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列
最长公共子序列
(1)给定2个序列X={x1,…,xm}和Y={y1,…,yn},要求找出X和Y的一个最长公共子序列
(2)例如:已知X = {A, G, C, G, T, A, G},Y = {G, T, C, A, G, A},求序列X和序列Y的最长公共子序列,有以下四种:
public class Blog {
public static void main(String[] args) {
//数组从1开始,舍弃0
char[] x= {' ','A','C','D','E','D','C'};
char[] y= {' ','A','B','C','D','C'};
System.out.println(LCS(x, y, x.length-1, y.length-1, new int[x.length][y.length]));
}
/**
*
* @param x 序列X
* @param y 序列Y
* @param i X下标
* @param j Y下标
* @param c 备忘录表
* @return 最长子序列的长度
*/
public static int LCS(char[] x,char[] y,int i,int j,int[][] c){
if (i == 0||j == 0){
return 0;
}
if (c[i][j] == 0){
if (x[i] == y[j]){
c[i][j] = LCS(x,y,i-1,j-1,c)+1;
}else {
c[i][j] = Math.max(LCS(x,y,i-1,j,c),LCS(x,y,i,j-1,c));
}
}
return c[i][j];
}
}
动态规划求解其实就是填一张表,很巧妙的用到了上面的公式:
c
[
i
,
j
]
=
{
0
,
i
=
0
,
j
=
0
c
[
i
−
1
,
j
−
1
]
+
1
,
x
i
=
y
j
M
a
x
(
c
[
i
−
1
,
j
]
,
c
[
i
,
j
−
1
]
)
,
x
i
≠
y
j
(4.2)
c[i,j]=
假设:X = {A, B, C, B, D, A, B},Y = {B, D, C, A, B, A},下面就是我们需要填的表格:
接下来的表格该怎么填?
(1)找到对应的
X
i
和
Y
j
X_i和Y_j
Xi和Yj,看是否相等,代入公式即可。
(2)相等情况:
x
i
=
y
j
时
,
c
[
i
]
[
j
]
=
c
[
i
−
1
,
j
−
1
]
+
1
x_i= y_j时,c[i][j]=c[i-1,j-1]+1
xi=yj时,c[i][j]=c[i−1,j−1]+1,该方格填入的值就是左上方方格的值+1
(3)不相等情况:
x
i
≠
y
j
时
,
c
[
i
]
[
j
]
=
M
a
x
(
c
[
i
−
1
,
j
]
,
c
[
i
,
j
−
1
]
)
x_i\ne y_j时,c[i][j]=Max(c[i-1,j],c[i,j-1])
xi=yj时,c[i][j]=Max(c[i−1,j],c[i,j−1]),该方格填入的值为上门方格和左边方格中的最大值。
最长子序列的长度是表格的最右下角的值。
代码实现:
public class Blog {
//测试:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String line1 = scanner.nextLine();
String line2 = scanner.nextLine();
char[] chars1 = line1.toCharArray();
char[] chars2 = line2.toCharArray();
System.out.println(lcsLength(chars1, chars2));
}
}
public static int lcsLength(char[] x,char[] y){
int m = x.length;
int n = y.length;
//要填的表
int[][] p = new int[m+1][n+1];
//表的第1行和第一列全部为0
for (int i = 0; i <= m; i++) {
p[i][0]=0;
}
for (int i = 0; i <= n; i++) {
p[0][i]=0;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (x[i-1]==y[j-1]){
p[i][j] = p[i-1][j-1]+1;
}else {
p[i][j] = Math.max(p[i-1][j],p[i][j-1]);
}
}
}
return p[m][n];
}
}
我们可以定义一个数组来判断表格中的数据来自那个方向,是从左上角,还是左边,还是右边。
定义了方向怎么求解,看下面举例的表格,斜线下的数字表示方向,1表示↘,2表示 ↓,3表示→。我们从表格的最后一个元素开始遍历,最后一个元素4/2
,2表示数据来自上面,找到上面的元素4/1
,1表示来自左上角,且是因为
X
i
=
Y
j
X_i=Y_j
Xi=Yj,也就是最长的公共子序列包含这个元素,继续遍历直到 i 和 j 等于0,然后输出方向为1的元素就是我们的最长公共子序列。
代码实现:
package test.gaolang.blog3;
import java.util.List;
import java.util.Scanner;
/**
* @author DELLHL
*/
public class Blog {
//测试:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String line1 = scanner.nextLine();
String line2 = scanner.nextLine();
char[] chars1 = line1.toCharArray();
char[] chars2 = line2.toCharArray();
int[][] b = lcsLength(chars1, chars2, new int[chars1.length + 1][chars2.length + 1]);
printlcs(chars1.length ,chars2.length ,chars1,b);
System.out.println();
}
}
public static int[][] lcsLength(char[] x,char[] y,int[][] b){
int m = x.length;
int n = y.length;
int[][] p = new int[m+1][n+1];
for (int i = 0; i <= n; i++) {
p[0][i] = 0;
}
for (int i = 0; i <= m; i++) {
p[i][0] = 0;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (x[i-1]==y[j-1]){
b[i][j] = 1;
p[i][j] = p[i-1][j-1]+1;
}else if (p[i-1][j]>=p[i][j-1]){
b[i][j] = 2;
p[i][j] = p[i-1][j];
}else {
b[i][j] = 3;
p[i][j] = p[i][j-1];
}
}
}
return b;
}
public static void printlcs(int i,int j, char[]a,int [][]drection) {
if(i==0||j==0) {
return;
}
if(drection[i][j]==1) {
//下面两句代码的位置不能调换,调换就相当于逆序输出,全部递归完后,从第一个开始输出
printlcs(i-1,j-1,a,drection);
System.out.print(a[i-1]);
}
else if(drection[i][j]==2) {
printlcs(i-1,j,a,drection);
}else {
printlcs(i,j-1,a,drection);
}
}
}
p[i][j]
时,只用到数组p的第i
行和第i-1
行。用2行的数组空间就可以计算出最长公共子序列的长度,可将空间需求减至O(min(m,n))public class Blog {
//测试:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String line1 = scanner.nextLine();
String line2 = scanner.nextLine();
char[] chars1 = line1.toCharArray();
char[] chars2 = line2.toCharArray();
System.out.println(lcsLength(chars1, chars2));
}
}
public static int lcsLength(char[] x,char[] y){
int m = x.length;
int n = y.length;
//要填的表
int[][] p = new int[2][Math.min(m,n)+1];
//表的第1行和第一列全部为0
for (int i = 0; i <= 1; i++) {
p[i][0]=0;
}
for (int i = 0; i < Math.min(m,n); i++) {
p[0][i]=0;
}
int flag = 0;
for (int i = 1; i <= Math.max(m,n); i++) {
flag = 1 - flag;
for (int j = 1; j <= Math.min(m,n); j++) {
if (x[i-1]==y[j-1]){
p[flag][j] = p[1-flag][j-1]+1;
}else {
p[flag][j] = Math.max(p[1-flag][j],p[flag][j-1]);
}
}
}
return p[flag][Math.min(m,n)];
}
}
public class Test {
public static void main(String[] args) {
int[] a = {-2,11,-4,13,-5,-2};
System.out.println(solve(a));
}
public static int solve(int[] a){
int[] p = new int[a.length];
//以第一个结尾
p[0] = a[0];
int max = p[0];
for (int i = 1; i < a.length; i++) {
//从第二个开始
if (p[i-1] > 0){
p[i] = a[i] +p[i-1];
}else {
p[i] = a[i];
}
if (p[i] > max){
max = p[i];
}
}
return max;
}
}
两个字符串,输出最长公共子串的长度。
算法思路:
两个字符串,以第一个字符串为基准,第二个字符跟它进行比较,例如"ABCDEF"
和"BDEF"
找最长公共子序列。
找第一个字符串中第一个字符A
是否在"BDEF"
中,如果存在,继续比较两个字符串中A
字符后面的字符,相等接着比较下一个,记录好长度,循环第一个字符串中的字符与第二字符串比较,最后就可以求出所有的公共子串的长度,选择最大的输出。
public class Main {
//公共子串
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
//多组输入测试:
while (scanner.hasNext()){
String line = scanner.nextLine();
String line2 = scanner.nextLine();
char[] chars = line.toCharArray();
char[] chars2 = line2.toCharArray();
//记录最大长度
int max = 0;
//两个字符串,只要有一个为空,我们的长度为0,直接输出
if (chars.length==0||chars2.length==0){
System.out.println(0);
}else {
//以第一个字符
for (int i = 0; i < chars.length; i++) {
for (int j = 0; j < chars2.length; j++) {
int m = i;
int k = j;
int len = 0;
while(m < chars.length&&k <chars2.length&&chars[m]==chars2[k]){
len++;
m++;
k++;
}
if (len > max){
max = len;
}
}
}
System.out.println(max);
}
}
}
}
类似前面最长公共子序列的求法,填一张表,第0行和第0列为为0,遇到
X
i
=
Y
j
X_i=Y_j
Xi=Yj的方格的值等于左上角方格加1,不相等为0,记录一下表格中的最大值为我们的最长公共子串的长度。
代码:
public class Test2 {
public static void main(String[] args) {
String x = "acbcbcef";
String y = "abcbced";
System.out.println(LCS(x, y));
}
public static int LCS(String x,String y){
char[] a = x.toCharArray();
char[] b = y.toCharArray();
int m = a.length;
int n = b.length;
int max = 0;
int[][] p = new int[m+1][n+1];
for (int i = 0; i <= m; i++) {
p[i][0] = 0;
}
for (int i = 0; i <= n; i++) {
p[0][n] = 0;
}
for (int i = 1; i < m+1; i++) {
for (int j = 1; j < n+1; j++) {
if (a[i-1]==b[j-1]){
p[i][j] = p[i-1][j-1]+1;
if (p[i][j] > max){
max = p[i][j];
}
}else {
p[i][j] = 0;
}
}
}
return max;
}
}
如果要把这个公共子串输出,我可以在上面算法的基础上记录一下最大值的横坐标或者纵坐标。
public class Test2 {
public static void main(String[] args) {
String x = "acbcbcef";
String y = "abcbced";
System.out.println(LCS(x, y));
}
public static int LCS(String x,String y){
char[] a = x.toCharArray();
char[] b = y.toCharArray();
int m = a.length;
int n = b.length;
int max = 0;
int index = 0;
int[][] p = new int[m+1][n+1];
for (int i = 0; i <= m; i++) {
p[i][0] = 0;
}
for (int i = 0; i <= n; i++) {
p[0][n] = 0;
}
for (int i = 1; i < m+1; i++) {
for (int j = 1; j < n+1; j++) {
if (a[i-1]==b[j-1]){
p[i][j] = p[i-1][j-1]+1;
if (p[i][j] > max){
max = p[i][j];
index = i;
}
}else {
p[i][j] = 0;
}
}
}
for (int i = max; i > 0; i--) {
System.out.print(a[index-i]);
}
System.out.println();
return max;
}
}
给定n个矩阵 A 1 , A 2 , . . . , A n {{A_1,A_2,...,A_n}} A1,A2,...,An ,其中 A i A_i Ai与 A i + 1 A_{i+1} Ai+1 是可乘的, i = 1 , 2 , . . . , n − 1 i=1,2,...,n-1 i=1,2,...,n−1
考察n个矩阵的连乘积 A 1 , A 2 , . . . , A n A_1,A_2,...,A_n A1,A2,...,An
矩阵乘法满足结合律——》计算矩阵的连乘可以有许多不同的计算次序——》计算次序可以用加括号的方式来确定
若一个矩阵连乘积的计算次序完全确定(该连乘积已完全加括号)——》可依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积
如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少
我们可以看到四个矩阵连乘,顺序不一样,计算乘法的次数也不一样,我们需要找出计算乘法最少的顺序。
A[i:j]
,
i
≤
j
i≤j
i≤jA[i:j]
的最优计算次序:设这个计算次序在矩阵
A
k
A_k
Ak和
A
k
+
1
A_{k+1}
Ak+1之间将矩阵链断开,
i
≤
k
<
j
i≤k<j
i≤k<j,则其相应完全加括号方式为
(
A
i
A
i
+
1
.
.
.
A
k
)
(
A
k
+
1
A
k
+
2
.
.
.
A
j
)
(A_iA_{i+1}...A_k)(A_{k+1}A_{k+2}...A_j)
(AiAi+1...Ak)(Ak+1Ak+2...Aj)A[i:k]
的计算量加上A[k+1:j]
的计算量,再加上A[i:k]
和A[k+1:j]
相乘的计算量(三部分组成)A[i:j]
的最优次序所包含的计算矩阵子链 A[i:k]
和A[k+1:j]
的次序也是最优的A[i:k]
和A[k+1:j]
相乘的计算量
7. 代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextInt()){
int n = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
System.out.println(matrixChain(a, new int[a.length-1][a.length-1]));
}
}
/**
* 矩阵连乘
* @param p 连乘矩阵
* @param m 最小解
*/
public static int matrixChain(int[] p,int[][] m){
//矩阵个数
int n = p.length-1;
//i=j的情况 m[i][i]:自有一个矩阵,没有乘法
for (int i = 0; i < n; i++) {
m[i][i] = 0;
}
//i < j的情况
for (int i = 1; i < n; i++) {
int k = i;
for (int j = 0; j < n-i; j++) {
//从j分开 m[j][j](=0)和m[j+1][k] ,合并的乘法次数:p[j]xp[j+1]与p[j+1]xp[k+1]
m[j][k] = m[j+1][k]+ p[j]*p[k+1]*p[j+1];
//m[j][k] 从j+1开始分开,到k-1结束,选出乘法次数最少的
for (int l = j+1; l < k; l++) {
int t = m[j][l]+m[l+1][k] +p[j]*p[k+1]*p[l+1];
if (t < m[j][k]){
m[j][k] = t;
}
}
k++;
}
}
//m[0][n-1]:第一个矩阵到第n个矩阵连乘的最少次数
return m[0][n-1];
}
}
import java.util.Scanner;
/**
* @author DELLHL
*/
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextInt()){
int n = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
int[][] ints = matrixChain(a, new int[a.length - 1][a.length - 1], new int[a.length - 1][a.length - 1]);
/* for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-1; j++) {
System.out.print(ints[i][j]);
}
System.out.println();
}*/
traceback(ints,0,n-2);
}
}
/**
* 矩阵连乘
* @param p 连乘矩阵
* @param m 最小解
* @param s 记录分割的值
*/
public static int[][] matrixChain(int[] p,int[][] m,int[][] s){
//矩阵个数
int n = p.length-1;
//i=j的情况 m[i][i]:自有一个矩阵,没有乘法
for (int i = 0; i < n; i++) {
m[i][i] = 0;
s[i][i] = 0;
}
//i < j的情况
for (int i = 1; i < n; i++) {
int k = i;
for (int j = 0; j < n-i; j++) {
//从j分开 m[j][j](=0)和m[j+1][k] ,合并的乘法次数:p[j]xp[j+1]与p[j+1]xp[k+1]
m[j][k] = m[j+1][k]+ p[j]*p[k+1]*p[j+1];
s[j][k] = j;
//m[j][k] 从j+1开始分开,到k-1结束
for (int l = j+1; l < k; l++) {
int t = m[j][l]+m[l+1][k] +p[j]*p[k+1]*p[l+1];
if (t < m[j][k]){
m[j][k] = t;
s[j][k] = l;
}
}
k++;
}
}
return s;
}
public static void traceback(int[][] s,int i,int j){
//只有一个矩阵 ==》结束
if (i == j){
return;
}
//[i,j]是从s[i][j]分开的,写成两个部分
traceback(s,i,s[i][j]);
traceback(s,s[i][j]+1,j);
//因为我们i是从0开始的,所以 i和j输出时都需要加1,分开也是从0开始,比如s[i][j]=i表示分成[i][i]和[i+1][j]
System.out.println("A["+(i+1)+":"+(s[i][j]+1)+"] * A["+(s[i][j]+2)+":"+(j+1)+"]");
}
}
动态规划解题思路:
设dp[i]
是在a[i]
为单调递增子序列最后一个元素时,所得最长单调递增子序列的长度为
d
p
[
i
]
=
{
1
,
i
=
1
M
a
x
(
d
p
[
j
]
)
+
1
,
a
[
j
]
<
a
[
i
]
且
1
<
=
j
<
i
(1.1)
dp[i]=
解释一个公式:
1)单调调递增子序列最后一个元素最第1个的时候,长度只能为1
2)最后一个元素不为1时,长度等于前面的元素值中小于当前元素值且对应dp数组最大的元素a[j]
,
d
p
[
i
]
=
d
p
[
j
]
+
1
dp[i]=dp[j]+1
dp[i]=dp[j]+1
具体例题:输出这个{ 1, 7, 3, 5 ,9, 4 ,8}
最长子序列的长度:
填表:
1)设置dp
数组的初始值为1
2)i=1
时dp[1]=1
2)后面的值 =
比当前a[i]
小的中dp
最大的+1
,LIS为dp数组中最大的值。
代码实现:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
//多组测试
while (scanner.hasNextInt()){
//序列长度
int n = scanner.nextInt();
//序列
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
System.out.println(LIS(a));
}
}
public static int LIS(int[] a){
int[] dp = new int[a.length];
int max = 1;
for (int i = 0; i < a.length; i++) {
//初始化为 1
dp[i] = 1;
//以 i结尾,最长递增序列长度
for (int j = 0; j < i; j++) {
if (a[j] < a[i]){
if (dp[j] >= dp[i]){
dp[i] = dp[j]+1;
}
}
}
if (dp[i] > max){
max = dp[i];
}
}
return max;
}
}
测试:
7 #输入
1 7 3 5 9 4 8 #输入
4 #输出
定义一个索引数组,方便查找前一个数的下标。
import java.util.Scanner;
import java.util.Stack;
public class Main3 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextInt()){
int n = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
LIS(a);
}
}
public static int LIS(int[] a){
int[] dp = new int[a.length];
int[] pre = new int[a.length];
int max_index = 1;
int max = 1;
pre[0] = -1;
for (int i = 0; i < a.length; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (a[j] < a[i]){
if (dp[j] >= dp[i]){
dp[i] = dp[j]+1;
pre[i] = j;
}
}
}
if (dp[i] > max){
max = dp[i];
max_index = i;
}
}
Stack<Integer> stack = new Stack<>();
//递增子序列最长时 最后一个元素为max_index
int index = max_index;
while (index >= 0){
//入栈
stack.push(a[index]);
//前一个下标
index = pre[index];
}
int size = stack.size();
for (int i = 0; i < size; i++) {
//一一出栈
System.out.print(stack.pop()+" ");
}
System.out.println();
return max;
}
}
测试:
7 #输入
1 7 3 5 9 4 8 #输入
1 3 5 9 #输出
n=5, c=10, w={2, 2, 6, 5, 4}, v={6, 3, 5, 4, 6}
选中1,2,5三件物品,最高价值15,总重8
填表,物品的种类为表行,背包容量+1为行,从后面填到前面,j
表示背包容量。
最后一行:
public class Knapsack {
public static void main(String[] args) {
//浪费数组的第一个
int[] w = {0,2, 2, 6, 5, 4};
int[] v = {0,6, 3, 5, 4, 6};
System.out.println(fun(5, 10, v, w));
}
public static int fun(int n,int c,int[] v,int[] w){
int[][] m = new int[n+1][c+1];
//防止数组越界
int jMax = Math.min(w[n]-1,c);
//Step1:填最后一行
//j<w[n] ==>m[n][j]=0
for (int j = 0; j <= jMax; j++) {
m[n][j] = 0;
}
//j>=w[n] ==>m[n][j]=v[n]
for (int j = w[n]; j <= c; j++) {
m[n][j] = v[n];
}
//Step2: 从倒数第二行往前面填
for (int i = n-1; i > 1; i--) {
jMax = Math.min(w[i]-1,c);
for (int j = 0; j <= jMax; j++) {
m[i][j] = m[i+1][j];
}
for (int j = w[i]; j <= c; j++) {
m[i][j] = Math.max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
}
}
//第一行最后一个元素
m[1][c] = m[2][c];
if (c >= w[1]){
m[1][c] = Math.max(m[1][c],m[2][c-w[1]]+v[1]);
}
return m[1][c];
}
}
//根据填的表格推断
public static void traceback(int[][] m,int n,int c,int[] w){
int[] x = new int[n+1];
for (int i = 1; i < n; i++) {
//没有选择
if (m[i][c] == m[i+1][c] ){
x[i] = 0;
}else {
x[i] = 1;
c -= w[i];
}
}
x[n] = (m[n][c]>0) ? 1:0;
for (int i =1 ; i < x.length; i++) {
System.out.print(x[i]+" ");
}
System.out.println();
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。