2013年03月1日
最近在看《编程之美》,上面有一个题目是求字符串的相似度,书上给的解法使用递归,但是通过递归的结果我们可以看到明显有重复的结果。作者提出这个问题,并让我们解决,我网上找到一些说法,可以用动态规划。由于大学的时候把这些东西都忘记了,没办法就重新读《算法导论》第15章《动态规划》。
以下的这几句话是摘自书上的
和分治法一样,动态对话(dynamic programming)是通过组合子问题的解而解决整个问题的。分治算法是指将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的接。与此不同,动态对话是用于子问题不是独立的情况, 也就是各自问题包含公共的子子问题。在这种情况下, 若用分治法则会做许多不必要的工作,即重复求解公共子子问题。动态对话算法对每个子子问题只求解一下,将其结果保存在一张表中,从而可以避免每次遇到各个子问题时重复计算答案;
动态规划通常应用于最优化问题。此类问题可能有多种可行解。每个解有一个值,而我们希望找出一个具有最优(最大或最小)值的接。称这样的解为该问题的“一个”最优解(而不是“确定“的”最优解“),因为可能存在多个取最优值的解。
动态规划算法的设计可以分为如下4个步骤:
1)描述最优解的结构;
2)递归定义最优解的值;
3)按自底向上的方式计算最优解的值;
4)由计算出的结果构造一个最优解。
第1~3步构成问题的动态规划解的基础。第4步只要求计算最优解的值时可以略去。如果的确做了第4步,则有时要在第3步的计算中记录一些附加信息,是构造一个最优解变得更容易;
书上提到的第一个问题就是
1、装配线调度
上图为书上描述的问题:
我在设计程序中,为了和C++中的数组一直,数组下标0表示上图的1,1表示上图的2.....依次类推;
分析:
/*
* 用动态规划求最快的装配线
* 有两条装配线(i\k),每条装配线上有j个装配站点,在每个装配站点装配机器所需要的时间为a[i][j],
* 在装配过程中,可以由一条装配线i的站点j-1移动到另外一条装配线k,所消耗的时间为t[i][j]
* (表示从装配线i中站点j-1移动到另一条装配线k中站点j所消耗的时间;
* F[i][j] : 表示到达装配线i上的站点j,装配零件所需要的时间(当然包括a[i][j])
* a[i][j] : 表示零件在装配线i上的站点j装配零件所需要的时间
* e[i] : 表示从进入装配线i所需要的时间
* x[i] : 表示从装配线i离开所需要的时间
* F[i][j] = e[i] 其中 j = 1; (递归式)
* 或者 min(F[i][j-1] + a[i][j], S[k][j-1] + t[i][j] + a[i][j]); 其中j != 1,i\k分别是两条装配线
* 以上就是递归式
* 表示:当零件到达装配线i上的站点j装配零件所需要的时间为:
* 1、当j = 1时,为e[i]
* 2、当j != 1时,算出零件达到装配线i上的站点j-1装配零件所需要的时间 + 在装配线i上站点j装配零件所需要的时间
* 算出零件到达装配线k上的站点j-1装配零件所要的时间 + 从装配线k上的站点j-1移动到装配线i的站点j所要的时间 + 在装配线i上站点j装配零件所需要的时间
* 取两者的最小值
*/
#include <iostream> using namespace std; int n = 6; //站点数 int e[2] = {2, 4}; //进入两个装配线所耗的时间 int x[2] = {3, 2}; //离开两个装配线所耗的时间 int a[2][6] = {{7, 9, 3, 4, 8, 4}, //在装配线i的站点j装配所要的时间 {8, 5, 6, 4, 5, 7}}; int t[2][6] = {{0, 2, 3, 1, 3, 4}, //在装配线k上站点j-1离开到装配线i的站点j所需要的时间 {0, 2, 1, 2, 2, 1}}; int F[2][6] = {{0}, {0}}; //在装配线i上的站点j所需要的最少时间 int S[2] = {0}; //在装配线i上完成所需要的最少时间 int l[2][6] = {{0},{0}}; //到达装配线i上的站点j所需最少时间时,其上一站所在的装配线 int exitLine = 0 ; //当从最后一个装配站点处来的时候,这个装配站点所在的装配线 /* * 主要算法 */ int fastestWay(int F[][6], int a[][6], int t[][6]) { F[0][0] = e[0] + a[0][0]; F[1][0] = e[1] + a[1][0]; for(int i = 1; i< n; i++) { //到达装配线0的站点i所需要的最少时间 if( (F[0][i-1] + a[0][i]) > (F[1][i-1] + t[1][i] + a[0][i])) { F[0][i] = F[1][i-1] + t[1][i] + a[0][i]; l[0][i] = 1; //记录上一个站点i-1所在的线路 } else { F[0][i] = F[0][i-1] + a[0][i]; l[0][i] = 0; } //到达装配线1的站点i所需要的最少时间 if ((F[1][i-1] + a[1][i]) > (F[0][i-1] + t[0][i] + a[1][i])) { F[1][i] = F[0][i-1] + t[0][i] + a[1][i]; l[1][i] = 0; } else { F[1][i] = F[1][i-1] + a[1][i]; l[1][i] = 1; } } S[0] = F[0][5] + x[0]; S[1] = F[1][5] + x[1]; //输出达到任意一条装配线上的站点i所需要的最短时间 for(int i = 0; i<=1; i++) { for(int j= 0; j<n; j++) cout<<F[i][j]<<" "; cout<<endl; } for(int i = 0; i<=1; i++) { for(int j = 0; j<n; j++) cout<<l[i][j] + 1<<" "; cout<<endl; } return min(S[0], S[1]); } /* *打印最快的路线 */ void print() { if(S[0]> S[1]) { exitLine = 2; int line = 2; int station = n; cout<<"装配线"<<line<<", 站点"<<station<<endl; for (int i = n-1 ; i>0; i--) { line = l[line][i]; cout<<"装配线"<<line + 1<<", 站点"<<i<<endl; } } else { exitLine = 1; int line = 1; int station = n; cout<<"装配线1"<<", 站点"<<n<<endl; for(int i = n-1; i>0; i--) { //上一个站点所在的装配线 line = l[line][i]; cout<<"装配线"<<line + 1<<", 站点"<<i<<endl; } } cout<<endl; } /* * 递归输出线路 * line : 装配线 * station : 站点 */ void printRecur(int line, int station) { if (station == 0) { return; } else { int newLine = l[line - 1][station-1]; //求上一个站点的所在线路 printRecur(newLine + 1, station-1); cout<<"装配线"<<line<<", 站点"<<station<<endl; } } void main() { int fastWay = fastestWay(F, a, t); cout<<"最快的时间为:"<<fastWay<<endl; cout<<"逆序输出路线:"<<endl; print(); cout<<"顺序输出路线:"<<endl; printRecur(exitLine, n); system("pause"); }运行结果: