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");
}
运行结果: