和分治算法一样,动态规划是通过组合子问题的解而解决整个问题的。但是与分治算法不同的是,动态规划算法适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题。动态规划通常用于最优化问题的求解。看一个问题是否适合采用动态规划算法,主要有两个标志
:最优子结构和
重叠子问题。
最优子结构:问题的一个最优解包含了子问题的最优解。
重叠子问题:当一个递归算法不断地调用同一问题时,我们说该最优子问题包含重叠子问题。
动态规划算法的设计步骤如下:
1.描述最优解的结构。
2.递归定义最优解的值。
3.按自底向上的方式计算最优解的值。
4.由计算出的结果构造一个最优解。
下面利用动态规划算法求解一些最优化问题,本文解决装配线调度问题,问题如下:
假设有2条生产线,每条生产线有6个装配点。两条生产线对应点的功能相同,但是时间有所差别。产品需要经过这6个点才能完成生产。在同一条生产线上,产品从一个装配点转到下一个装配点的时间可以忽略,但是从一条生产线到另一条生产线则需要时间消耗。我们的问题是如何找到最短时间的路径。
上面问题的一般模型如下图:
我们将上述一般模型实例化,可以转换为下图:
在上图中,f1[i]为到达第一条生产线中第i个装配站的时间(包括在第i站的时间);f2[i]为到达第二条生产线中第i个装配站的时间(包括在第i站的时间);l1[i]为到达第一条生产线中第i个装配站的上一站是哪条生产线;l2[i]为到达第一条生产线中第i个装配站的上一站是哪条生产线。f*为最终的最短时间,l*为产品最终在哪条生产线上完成生产。
算法思想如下:
正如上面所说,我们先分别计算到两个装配线的站点1的最短路径,然后计算到站点2的最短路径,直到最终的最短路径。因为到一个站点的最短路径可以由前一个站点的最短路径加上前一个站点到本站点的最短路径。换句话说,到站点6的最短路径最优解包含到站点5的最优解,到站点5的最短路径最优解包含到站点4的最优解,……依此类推,最终可以变为到站点1的最优解。
算法实现如下:
#include<stdio.h>
#include<stdlib.h>
void print_route(int *l1,int *l2,int lfinal,int n);
int Fastest_way(int *a1,int n1,int *a2,int n2,int *t12,int n3,int *t21,int n4,int e1,int e2,int x1,int x2,int *l1,int *l2,int lfinal );
void main()
{
int a1[]={7,9,3,4,8,4};//初始化各节点的时间消耗
int a2[]={8,5,6,4,5,7};
int t12[]={2,3,1,3,4};
int t21[]={2,1,2,2,1};
int e1=2;
int e2=4;
int x1=3;
int x2=2;
int n1=sizeof(a1)/sizeof(int);
int n2=sizeof(a2)/sizeof(int);
int n3=sizeof(t12)/sizeof(int);
int n4=sizeof(t21)/sizeof(int);
//printf("%d%d%d%d\n",n1,n2,n3,n4);
int l1[6]={0};//第一个元素没有使用,每个元素的值代表前一次所在的生产线
int l2[6]={0};
int lfinal=0;//表示产品最终在哪个条线完成装配
lfinal=Fastest_way(a1,n1,a2,n2,t12,n3,t21,n4,e1,e2,x1,x2,l1,l2,lfinal );
print_route(l1,l2,lfinal,n1);
}
/******************************************************\
函数功能:寻找最短时间路径
输入:各个节点的时间消耗
输出:最终完成装配所在的生产线
\******************************************************/
int Fastest_way(int *a1,int n1,int *a2,int n2,int *t12,int n3,int *t21,int n4,int e1,int e2,int x1,int x2,int *l1,int *l2,int lfinal )
{
int f1[6]={0};
int f2[6]={0};
int final=0;//为总的最短时间消耗
f1[0]=e1+a1[0];
f2[0]=e2+a2[0];
for(int i=1;i<n1;i++)
{
if((f1[i-1]+a1[i])<=(f2[i-1]+a1[i]+t21[i-1]))
{
f1[i]=f1[i-1]+a1[i];
l1[i]=1;
}
else
{
f1[i]=f2[i-1]+a1[i]+t21[i-1];
l1[i]=2;
}
if((f2[i-1]+a2[i])<=(f1[i-1]+a2[i]+t12[i-1]))
{
f2[i]=f2[i-1]+a2[i];
l2[i]=2;
}
else
{
f2[i]=f1[i-1]+a2[i]+t12[i-1];
l2[i]=1;
}
}
if((f1[n1-1]+x1)<(f2[n1-1]+x2))
{
final=f1[n1-1]+x1;
lfinal=1;
}
else
{
final=f2[n1-1]+x2;
lfinal=2;
}
//for(int i=0;i<6;i++)
// printf("%d ",f1[i]);
//printf("\n");
//for(int i=0;i<6;i++)
// printf("%d ",f2[i]);
//printf("\n");
//for(int i=1;i<6;i++)
// printf("%d ",l1[i]);
//printf("\n");
//for(int i=1;i<6;i++)
// printf("%d ",l2[i]);
//printf("\n");
return lfinal;
}
/**************************************************\
函数功能:逆向打印装配所经过的线路节点
输入: 记录经过的节点的数组了l1和l2、最终完成装配所在的生产线
输出: 打印装配所经过的路程
\**************************************************/
void print_route(int *l1,int *l2,int lfinal,int n)
{
int flag=0;
printf("line: %d ,station: %d\n",lfinal,n);
for(int i=n-1;i>0;i--)
{
if(i==n-1)
{
if(lfinal==1)
{
printf("line: %d ,station: %d\n",l1[i],i);
if(l1[i]==1)
flag=1;
else
flag=2;
}
else
{
printf("line: %d ,station: %d\n",l2[i],i);
if(l2[i]==1)
flag=1;
else
flag=2;
}
}
else
{
if(flag==1)
{
printf("line: %d ,station: %d\n",l1[i],i);
if(l1[i]==1)
flag=1;
else
flag=2;
}
else
{
printf("line: %d ,station: %d\n",l2[i],i);
if(l2[i]==1)
flag=1;
else
flag=2;
}
}
}
}