动态规划通常应用于最优解的问题,即在有多个解的问题中找到“一个”的最优解(这个解并非“确定不变”)。动态规划的核心:最优子结构和重叠子问题。
最优子结构:一个问题的最优解包含了子问题的最优解,通过解决子问题为整个问题的解决提供支持。
重叠子问题:通过每个子问题只解一次,把解保存在一个表中,让递归过程中重复的子问题跟快得到解决。
解题步骤:
1.找到问题的最优解结构。
2.递归定义最优解的值。
3.自底向上计算最优解的值。
4.通过计算的最构造最优解。
大部分动态规划的问题的难点在于发现问题中最优解的递归过程。先承认自己没有天才的头脑,因此记下自己所遇的题的解法以满足一己私欲吧,下文中突兀的符号,不清楚的题意,丑陋的代码……望大家见谅。
《算法导论》
生产线问题:f1[i]表示到生产线1的i装备站的最优时间。a1[i]在生产线1的i装备站上花的时间。t1[i]表示从生产线1的i装备站移到另一条生产线所花的时间。对应还有数据f2[i],a2[i],t2[i]。e,x分别进入生产线和离开生产线的时间。
递归解:f1[j] = e1 + a1[1] j=1;
=min(f1[j-1] + a1[j] , f2[j-1] + t2[j-1] + a1[j]) j>=2;
f1[j] = e2 + a2[1] j=1;
=min(f2[j-1] + a2[j] , f1[j-1] + t2[j-1] + a2[j]) j>=2;
实现:
public class ProductLine {
/**
* @author htorc
*/
public static void main(String[] args) {
int a1[] = {7,9,3,4,8,4};
int a2[] = {8,5,6,4,5,7};
int t1[] = {2,3,1,3,4};
int t2[] = {2,1,2,2,1};
int x[] = {3,2};
int e[] = {2,4};
int f1[] = new int[a1.length];
int f2[] = new int[a1.length];
f1[0] = a1[0] + e[0];
f2[0] = a2[0] + e[1];
int i;
for(i=0; i<t1.length; i++){
f1[i+1] = Math.min(f1[i] + a1[i+1] , f2[i] + t2[i] + a1[i+1]);
f2[i+1] = Math.min(f1[i] + a2[i+1] + t1[i] , f2[i] + a2[i+1]);
}
System.out.println(Math.min(f1[i]+x[0], f2[i]+x[1]));
}
}
矩阵连乘:A0*A1……An适当添加括号使计算次数最少。 p[i],p[i+1]表示矩阵Ai的行数和列数。
递归解:m[i][j]表示Ai……Aj最少计算次数,s[i][j]表示分割点k的位置。
m[i][j] = 0 i=j
= min{m[i][k] + m[k+1][j] + pi*pk*pj}( i<k<=j) i<j
实现:
public class Matrix {
/**
* @author ht
*/
public static void print(int[][] s, int i, int j){
if(i == j)
System.out.print("A" + i);
else{
System.out.print("(");
print(s, i, s[i][j]-1);
print(s, s[i][j], j);
System.out.print(")");
}
}
public static void main(String[] args) {
int[] p = {30,35,15,5,10,20,25};
long[][] m = new long[p.length-1][p.length-1];
int[][] s = new int[p.length-1][p.length-1];
long min_value,sum;
int min_key;
for(int i=0;i<p.length-1;i++){
m[i][i] = 0;
s[i][i] = 0;
}
for(int i=1; i<p.length-1; i++){
for(int j=0; j<p.length-i-1; j++){
min_value = Long.MAX_VALUE;
min_key = j+1;
for(int k=1; k<=i; k++){
sum = m[j][j+k-1]+m[j+k][i+j] + p[j]*p[j+k]*p[i+j+1];
if(sum<min_value){
min_value = sum;
min_key = j+k;
}
}
m[j][j+i] = min_value;
s[j][j+i] = min_key;
}
}
System.out.println(m[0][p.length-2]);
for(int i=0; i<p.length-1; i++){
for(int j=0; j<p.length-1; j++){
System.out.print(s[i][j] + " ");
}
System.out.println();
}
print(s,0,p.length-2);
}
}
最长公共子串:X = {x1,x2,^,xm} 和 Y = {y1,y2,^,ym} 最长的公共子串。例如 :“BCA”是X="ABCBDAB" Y="BDCABA"的公共子串,但不是最长的。
递归解:m[i][j]表示x的前i个数遇到y的前j个数是的最长子串长度。s[i][j]记录通过何种途径时x取到第i个数,y取到第j个数。
m[i][j] = 0 i=0 || j=0;
= m[i-1][j-1] + 1 x[i] = y[j];
= Max(m[i-1][j] , m[i][j-1]) x[i] != y[j];
s[i][j] = 0 x[i-1] = y[j-1] ; 表示有公共元素加入,Zk-1是Xi-1和Yj-1的最长子串。
= 1 c[i-1][j] <= c[i][j-1] ; 表示X的前i个元素和Y的前j个组成的公共子集Zk蕴含Xi和Yj-1的一个最长子串。
= -1 c[i-1][j] <= c[i][j-1] ; 表示公共子集Zk蕴含Xi-1和Yj的一个最长子串。
实现:
public class LCS {
/**
* @author ht
*/
public static void init(char[] x, char[] y, int[][] m, int[][] s){
int l = x.length;
int w = y.length;
for(int i=0; i<=l; i++){
m[i][0] = 0;
s[i][0] = 0;
}
for(int j=0; j<=w; j++){
m[0][j] = 0;
s[0][j] = 0;
}
for(int i=1; i<=l; i++){
for(int j=1; j<=w; j++){
if(x[i-1] == y[j-1]){
m[i][j] = m[i-1][j-1] + 1;
s[i][j] = 0;
} else {
if(m[i][j-1] > m[i-1][j]){
m[i][j] = m[i][j-1];
s[i][j] = -1;
} else {
m[i][j] = m[i-1][j];
s[i][j] = 1;
}
}
}
}
}
public static void print(char[] x,int[][] s,int i, int j){
if(i==0 || j==0) return;
if(s[i][j] == 0){
print(x,s,i-1,j-1);
System.out.print(x[i-1] + " ");
} else if(s[i][j] == 1){
print(x,s,i-1,j);
} else {
print(x,s,i,j-1);
}
}
public static void main(String[] args) {
char[] y = "BDCABA".toCharArray();
char[] x = "ABCBDAB".toCharArray();
int[][] m = new int[x.length+1][y.length+1];
int[][] s = new int[x.length+1][y.length+1];
init(x,y,m,s);
print(x,s,x.length,y.length);
/*for(int i=0; i<m.length; i++){
for(int j=0; j<m[i].length; j++){
System.out.print(s[i][j] + " ");
}
System.out.println();
}*/
}
}