最近学习到模拟退火算法,遇到工作指派问题,但是查到网上的c++代码并不能解决问题,于是在它的基础上进行修改,完美解决了4工作4工人工作指派问题。
n个工作可以由n个工人分别完成。工人i完成工作j的时间为dij ,问如何安排可使总的工作时间达到最小。试按SA思想设计一个该问题的求解算法,并利用计算机语言实现设计的算法。
(1)假设有四个工人,四个工作,每个工人完成每项工作的时间如下表所示:
1 |
2 |
3
扫描二维码关注公众号,回复:
3714467 查看本文章
|
4 |
|
1 |
10 |
9 |
8 |
7 |
2 |
3 |
4 |
5 |
6 |
3 |
2 |
1 |
1 |
2 |
4 |
4 |
3 |
5 |
6 |
于是工人的工作时间构成了一个4×4的矩阵D[i][j],称为工作时间矩阵。
(2)这四个工作由四个工人分别完成,意思是每个工人只能做其中一个工作,一个工作只需由一个工人完成。例如1->3->2->4表示第1,3,2,4个工人分别完成第i个工作(i=1,2,3,4)。
(3)用p[i]存放工人序号,工作顺序保持1,2,3,4不变。这样求解总工作时间最=最优解时,只需要交换工人序号,D[p[i]][j]表示工人p[i]做工作j的时间。
则该问题的数学形式表示为:
求minimize的最优p[i]顺序
编码方式:顺序编码
初始解:随机产生
邻域移动方式:2-opt,即两两交换
初始温度:T0=2000
终止温度:Tf=0
降温函数:Tk+1=Tkr,r=0.95
内循环次数:In_loop=13000
三、算法流程
Step1:选定一个初始解1->2->3->4,初始温度:T0=2000,终止温度:Tf=0,令迭代指标k=0,Tk=T0。
Step2:随机产生邻域解,计算目标值增量
Step3(内循环流程):设置内循环停止条件,只要达不到内循环停止条件,则进行如下判断:
若,令cur=new;
否则产生,
若,令cur=new;
若,令cur=cur。
Step4:若达到内循环停止准则,跳转step5;否则跳转step2。
Step5:令k=k+1,更新Tk,然后是否满足外循环停止准则,若满足则停止,否则跳转step2。
该算法的程序实现可以用c++,c#,python等多种语言,本文采用c++进行编程。
/*********************************************************************** 模拟退火算法解决工作指派问题
***********************************************************************/
#include <iostream>
#include <cmath>
#include <time.h>
#include <stdio.h>
using namespace std;
const int MAXN = 50; //最大约束数n
const double INIT_T =2000; //初始温度
const double RATE = 0.95; //温度衰减率
const double FINAL_T = 0; //终止温度
const int IN_LOOP = 13000; //内层循环次数
const int OUT_LOOP = 20000; //外层循环次数
struct work { //定义工作结构类型
int p[MAXN]; //工人序号
double time; //时间
};
int N=4; //工人数量
double D[MAXN][MAXN]; //工作时间
int x[MAXN][ MAXN]; //选择工人与工作
work bestwork; //最优的遍历路径
void swap(int &a,int &b) //注意&a和&b 我们要的p[]里存储的数据
{
int t;
t=a;a=b;b=t;
}
inline double totaldist(work q) //计算总时间
{
int a,b,c,d;
double cost = 0;
a=q.p[0];
b=q.p[1];
c=q.p[2];
d=q.p[3];
cost=D[a][0]+D[b][1]+D[c][2]+D[d][3];
return cost;
}
void init() //读入数据,并初始化//输入时间矩阵D[i][j] 格式:工人i 工作j
{
int i=0,j=0;
for(i=0;i<N;i++)
{
for(j=0;j< N;j++)
cin>> D[i][j];
}
for (i=0; i<N; i++) //最优解的初始状态
{
bestwork.p[i] = i;
}
bestwork.time = totaldist(bestwork);
}
/*********************************************************************** 产生指派方法
***********************************************************************/
work getnext(work p) //新解产生函数
{
int x, y;
work ret;
ret = p;
do {
x = rand() % 4;
y = rand() % 4 ;//x,y产生0-3的随机数
}while(x == y);
swap(ret.p[x], ret.p[y]); //交换两工人位置顺序
ret.time = totaldist(ret);
return ret;
}
/*********************************************************************** 退火和降温过程
**********************************************************************/
work sa()
{
double T; //温度
work curwork,newwork; //当前指派和新指派
int i,A_t=0;
double delta;
T = INIT_T; //赋值初始温度
curwork = bestwork;
while(true) //内循环
{
for (i=1; i<=IN_LOOP; i++)
{
newwork = getnext(curwork); //获取新路径
delta = newwork.time - curwork.time;
if (delta < 0.0)
{
curwork = newwork;
}
else
{
double rnd = rand()%1001/1000; //随机产生0-1的浮点数
double p = exp(-delta/T);
if (p > rnd)
curwork = newwork;
else
curwork=curwork;
}
A_t++;
}
T = T * RATE; //降温
if ( A_t >= OUT_LOOP || T < FINAL_T) break; //外循环停止条件
}
return newwork;
}
/*********************************************************************** 程序主函数
*********************************************************************/
int main()
{
init();
printf("初始工作指派: %.4f\n", bestwork.time);
for(int i=0;i<N;i++)
{
printf(" %d->", bestwork.p[i]+1);
}
printf("\n");
bestwork=sa();
printf("最优工作指派: %.4f\n", bestwork.time);
for(int j=0;j<N;j++)
{
printf(" %d->",bestwork.p[j]+1);
}
printf("\n");
system("pause");
return 0;
}
最优指派方法:工人1,2,3,4分别做工作4,2,1,3。
这样的到最小总工作时间为18小时。