算法总体思想
动态规划(Dynamic Programming)是通过组合子问题的解而解决整个问题的。分治是指将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原始问题的解,与此不同,动态规划适用于子问题不是独立的情况,也就是各个子问题包含公共的子问题。在这种情况下,采用分治法会做许多不必要的工作,即重复地求解公共地子问题。动态规划算法对每个子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。
动态规划算法的基本要素
-、最优子结构
注意:同一个问题可以有多种方式刻划它的最优子结构,有些表示方法的求解速度更快(空间占用小,问题的维度低)
二、重叠子问题
问题举例
最长公共子序列
•若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij。例如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列,相应的递增下标序列为{2,3,5,7}。
•给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
•给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列。
最长公共子序列的结构
设序列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。其他情况下,由最优子结构性质可建立递归关系如下:
(上传好几次不成功)
计算最优值(伪代码)
AlgorithmlcsLength(x,y,b)
mßx.length-1;
nßy.length-1;
c[i][0]=0;c[0][i]=0;
for(inti= 1; i<= m;i++)
for(intj = 1; j <= n; j++)
if(x[i]==y[j])
c[i][j]=c[i-1][j-1]+1;
b[i][j]=1;
else if(c[i-1][j]>=c[i][j-1])
c[i][j]=c[i-1][j];
b[i][j]=2;
else
c[i][j]=c[i][j-1];
b[i][j]=3;
源代码实现(测试通过)
#include<iostream>
#define LEN_ARR_A 20
#define LEN_ARR_B 12
using namespace std;
/*
* brief : calculate longest common substring of two string and
* mark path of getting the longest common substring
* parameter : lenComStr : length of common substring
* solutionPath : mark of path of solution
*
* note: dynamic programming :caculate longest common substring between X(n)
* and Y(m-1),likey to caculate longest common substring between X(n)
* and Y(m-1) or between X(n-1) and Y(m). X(0) and Y(0) are not referenced!
*
*
* return : null
*/
template<class Type>
void LCSLength(size_t lenStrA, size_t lenStrB, Type *strA, Type *strB, size_t ** lenComStr, size_t ** solutionPath)
{
for(size_t i = 0; i < lenStrA; ++i)
lenComStr[i][0] = 0;
for(size_t i = 0; i < lenStrB; ++i)
lenComStr[0][i] = 0;
for(size_t i = 1; i < lenStrA; ++i)
for(size_t j = 1; j < lenStrB; ++j){
if(strA[i] == strB[j]){
lenComStr[i][j] = lenComStr[i -1][j - 1] + 1;
//global solution depends on local solution
solutionPath[i][j] = 1;
//mark path of getting the longest common substring
}
else if(lenComStr[i - 1][j] >= lenComStr[i][j - 1]){
lenComStr[i][j] = lenComStr[i - 1][j];
//global solution depends on local solution
solutionPath[i][j] = 2;
}else{
lenComStr[i][j] = lenComStr[i][j - 1];
//global solution depends on local solution
solutionPath[i][j] = 3;
}
}
}
/*
* brief : output longest common substring of two string
*
* parameter : i is beginning index of char array
* j is end index of char array
* solutionPath : mark of path of solution
*
* note: value of solutionPath[i][j] has 3 states
*
* return : null
*/
template<class Type>
void LCS(size_t i, size_t j, Type * strA, size_t ** solutionPath)
{
if(i == -1 || j == -1) return;
if(solutionPath[i][j] == 1){
LCS(i - 1, j - 1, strA, solutionPath);
cout << strA[i];
}else if(solutionPath[i][j] == 2)
LCS(i - 1, j, strA, solutionPath);
else{
LCS(i, j - 1, strA, solutionPath);
}
}
int main()
{
char strA[LEN_ARR_A] = { '0','b', 'c', 'e', 'f', 's', 'j', 'f', 'u', 'i', 'j', 'k', 'y', 'h', 'n', 't', 'r', 'd', 'e', 'm'};
char strB[LEN_ARR_B] = { '0', 'e', 'j', 'u', 'k', 'y', 'n', 'r', 'd', 'e', 'f', 's'};
size_t ** lenComStr = new size_t*[LEN_ARR_A];
size_t ** solutionPath = new size_t*[LEN_ARR_A];
for(size_t i = 0; i < LEN_ARR_A; ++i) {
lenComStr[i] = new size_t[LEN_ARR_B];
solutionPath[i] = new size_t[LEN_ARR_B];
}
LCSLength(LEN_ARR_A, LEN_ARR_B, strA, strB, lenComStr, solutionPath);
LCS(LEN_ARR_A - 1, LEN_ARR_B - 1, strA, solutionPath);
return 0;
}