一、动态规划
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题
与分治法不同的是。
动态规划经分解得到的子问题往往不是互相独立的。不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。
如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。
1.1动态规划的步骤
找出最优解的性质,并刻划其结构特征。
递归地定义最优值。
以自底向上的方式计算出最优值。
根据计算最优值时得到的信息,构造最优解。
1.2动态规划算法的要素
(1)最优子结构
矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。
在分析问题的最优子结构性质时,所用的方法具有普遍性:首先假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。
利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。最优子结构是问题能用动态规划算法求解的前提。
(2)重叠子问题
递归算法求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。这种性质称为子问题的重叠性质。
动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。
通常不同的子问题个数随问题的大小呈多项式增长。因此用动态规划算法只需要多项式时间,从而获得较高的解题效率。
二、动态规划的举例
2.1最长公共子序列问题
分析:
设序列X={x1,x2,…,xm}和Y={y1,y2,…,yn}的最长公共子序列为Z={z1,z2,…,zk} ,则
(1)若xm=yn,则zk=xm=yn,且zk-1是xm-1和yn-1的最长公共子序列。
(2)若xm≠yn且zk≠xm,则Z是xm-1和Y的最长公共子序列。
(3)若xm≠yn且zk≠yn,则Z是X和yn-1的最长公共子序列。
由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列。因此,最长公共子序列问题具有最优子结构性质。
递归结构:
由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系。用c[i][j]记录序列和的最长公共子序列的长度。其中, Xi={x1,x2,…,xi};Yj={y1,y2,…,yj}。当i=0或j=0时,空序列是Xi和Yj的最长公共子序列。故此时C[i][j]=0。其他情况下,由最优子结构性质可建立递归关系如下:
代码:
public class DPLCSLengthTest {
public static void main(String[] args) {
String str1 = "ABCYUDRTGHY";
String str2 = "ABYURETHY";
int[][] cz = new int[str1.length()][str2.length()];
int[][] m = new int[str1.length()][str2.length()];
int size = lcsLength(str1, str2, cz, m, str1.length()-1, str2.length()-1);
System.out.println("size:" + size);
for (int[] ints : cz) {
for (int anInt : ints) {
System.out.print(anInt + " ");
}
System.out.println();
}
System.out.println("--------------");
for (int[] ints : m) {
for (int anInt : ints) {
System.out.print(anInt + " ");
}
System.out.println();
}
//打印公共子序列的内容
backTrace(m, str1, str1.length()-1,str2.length()-1);
}
/**
* @param m
* @param str1
* @param i
* @param j
*/
private static void backTrace(int[][] m, String str1, int i, int j) {
if(i<0| j<0){
return;
}
if(m[i][j]==1){
backTrace(m,str1,i-1,j-1);
System.out.print(str1.charAt(i)+" ");
}else if(m[i][j]==2){
backTrace(m,str1,i,j-1);
}else if(m[i][j]==3){
backTrace(m,str1,i-1,j);
}
}
/**
*
* @param str1
* @param str2
* @param cz
* @param m
* @param i
* @param j
* @return
*/
private static int lcsLength(String str1, String str2, int[][] cz, int[][] m, int i, int j) {
if(i < 0 || j < 0){
return 0;
}
if(cz[i][j] > 0){
return cz[i][j];
}
if(str1.charAt(i) == str2.charAt(j)){
cz[i][j] = lcsLength(str1, str2, cz, m, i-1, j-1) + 1;
m[i][j] = 1;
return cz[i][j];
}
int max1 = lcsLength(str1, str2, cz, m, i, j-1);
int max2 = lcsLength(str1, str2, cz, m, i-1, j);
if(max1 > max2){
cz[i][j] = max1;
m[i][j] = 2;
} else {
cz[i][j] = max2;
m[i][j] = 3;
}
return cz[i][j];
}
}
2.2最长公共子串
与最长公共子序列不同的是最长公共子串是连续的
public class DPLCSMaxComStr {
int [][]getdp(char []arr1,char []arr2)
{
int [][]dp = new int [arr1.length][arr2.length];
for(int i = 0;i<arr1.length;++i) //第一列赋值
{
if(arr1[i] == arr2[0])
{
dp[i][0] = 1;
}
}
for(int j = 1;j<arr2.length;++j) // 第一行赋值
{
if(arr2[j]==arr1[0])
{
dp[0][j]=1;
}
}
for(int i = 1;i<arr1.length;++i) // 其余位置相等的赋值为 左上角加1 ==》当前子串长度
{
for(int j = 1;j<arr2.length;++j)
{
if(arr1[i]==arr2[j])
{
dp[i][j] = dp[i-1][j-1]+1;
}
}
}
return dp;
}
int findLargestSizeString(String str1, String str2){
if(str1==null || str2==null ||str1.equals("") ||str2.equals(""))
{
return -1;
}
char [] arr1 = str1.toCharArray();
char [] arr2 = str2.toCharArray();
int [][] dp= getdp(arr1,arr2);
int end = 0;
int maxlen = 0;
for(int i = 0; i<arr1.length;++i)
{
for(int j = 0;j<arr2.length;++j)
{
if(dp[i][j] > maxlen) // 如果所在值大于 maxlen
{
end = i; // end 即为结束子串的位置下标
maxlen = dp[i][j]; // 更新maxlen
}
}
}
String subs = str1.substring(end-maxlen+1, end+1);
System.out.println("两个字符串的最长相同子串:"+subs);
return maxlen;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
DPLCSMaxComStr main2=new DPLCSMaxComStr();
int maxlength=main2.findLargestSizeString("GCCCTAGCCAGDE", " GCGCCAGTGDE");
System.out.println((maxlength));
}
}