完成有顺序约束的任务指派问题--应用模拟退火算法求解

假设问题:

1、一个企业生产一件产品共有M项任务需要完成,依次记为A、B、C......,任务的完成需要遵循一定的顺序,A任务完成后才能去完成B,B完成后才能去完成C,以此类推。

2、此时有N(N>=M)名员工可以完成这些任务,员工编号依次为1、2、3....,且每个员工只能固定完成一项任务,由于工作能力不同,每名员工完成不同任务耗费的时间不一样,如表1所示。

表1 完成任务所需时间表
任务/时间12345
A2118162214
B3528332622
C1216111411

3、现按任务的顺序为各任务安排合适的员工,各员工按编号从小到大的顺序进行排队,对每一个员工可以做出同意或拒绝的选择,且只有一次机会,当选择同意时,按任务的先后顺序为此员工安排一项任务,当选择拒绝时,此员工离开队伍,后续不能再向其安排任务。

例如:

当前最新任务为A,1号员工到来,拒绝1号完成任务A,1号离开,2号员工到来,接受2号员工完成任务A

当前最新任务变为B,3号员工到来,拒绝3号完成任务B,3号离开,4号员工到来,接受4号员工完成任务B

当前最新任务变为C,5号员工到来,接受5号员工完成任务C

任务已分配完毕

因为条件3的限制,导致表1发生了一些变化,

1号员工不可能分配到任务B、C、...,因为这样就没有员工完成A了,同理有以下限制

2号员工不可能分配到任务C...

4号员工不可能分配到任务A...

5号员工不可能分配到任务A、B...

对于不可能分配到的任务,此员工完成时间记为无穷大,如下表2所示

表2 完成任务所需时间表
任务/时间12345
A211816
B283326
C111411

问如何为每一个任务分配一个员工去,能够达到生产一件产品耗费总时间最小?

分析:

理想情况下,应该为每一个任务安排耗时最短的员工完成,可以看到,3号员工完成任务A,4号员工完成任务B,5号员工完成任务C,刚好满足条件,而且完美错开,可以达到总耗时最短的要求,总耗时为16+26+11=53。但是如果错不开怎么办,即某一个员工在完成多件任务都是耗时最短的情况下,就不能这么简单处理了,所以要考虑一般的情况。

一般的,在这个问题中,任务的完成是固定顺序,员工的选择也是从前到后,那么组合的情况就是123、124、134、235等等。可以认为是从5个员工中选3个,然后按顺序排队,依次分配一个任务,可以选择的组合数为=10,对每一种组合下计算对应的耗费时间,即可得到耗时最短的一种组合情况。

Matlab程序如下:

% 文件名:example1.m
% 时间:2020年9月29日
% 作者:乐观的lishan
% 功能:遍历法计算顺序指派问题
clear,close,clc
warning off
tic

%生成代价矩阵
d = [21   18   16   inf   inf;
     inf  28   33   26    inf;
     inf  inf  11   14    11]; 
 
M = size(d,1);    %任务数 
N = size(d,2);    %员工数,员工应数大于或等于任务数

C = nchoosek(1:N,M);  %求出所有的组合情况
n = size(C,1);
Sum = zeros(n,1);
for i = 1:n
    for j = 1:M 
         Sum(i) = Sum(i) + d(j,C(i,j)); 
    end 
     
end
min_Sum = min(Sum);
min_plan = (C((Sum == min_Sum),:));
num_plan = size(min_plan,1);
toc

%输出结果
fprintf('完成指派任务的方案总数为:%.0f\n',n)
fprintf('完成指派任务耗费的最短时间为:%.0f\n',min(Sum))
for i = 1:num_plan
    fprintf('对应第%d种指派方案为:%s\n',i,num2str(min_plan(i,:)))
end
%绘制耗费时间坐标图
plot(Sum,'r','LineWidth',2)
%绘图说明
xlabel('组合编号')
ylabel('耗费时间')
title('不同组合方案下的时间耗费示意图')

求出结果如下:

求出结果为3号员工完成任务A,4号员工完成任务B,5号员工完成任务C,此时耗费时间最短,最短时间为53

但是这是数据量比较少的情况,一旦数据增大,就不能再使用遍历了,比如,任务数为27,员工数为35,组合方式就达两千三百万种之多,此时需要寻找一种更好的方法

更一般的,对于一个离散的组合优化问题,可以使用蒙特卡洛加模拟退火算法解决

算法步骤描述如下:

step1:使用蒙特卡洛方法生成随机选择一个最优的组合,作为初始解,并计算初始能量值(目标函数值)

step2:初始化模拟退火算法参数,初始温度T,终止温度e,温度衰减因子alf,马尔科夫链长度L,能量不再降低的次数上限cnt_limit

step3:马尔科夫链长度加1,判断是否达到上限,若是,算法结束,否则,开始step4

step4:当前温度下,在当前解的基础上进行扰动构造一个新解,本文中构造方法为:先将当前解的组合中随机去掉一个员工,再随机添加一个新员工。生成一个新解(一种组合),并计算能量值,即目标函数值(耗费总时间)

step5:计算与前一个解的能量之差

step6:根据能量差判断是否接受新解。若能量差小于0 ,说明新解的能量更低,接受新解;否则 ,说明新解的能量没有下降,以玻尔兹曼概率接受新解。同时记录最优解和对应的最低能量值(最小目标函数值)

step7:判断最低能量值是否保持不变,若是,计数器加1,否则,计数器清0。若计数器到达上限,算法结束。

step8:降低温度,判断温度是否达到下限,若是,算法结束,否则,转向step3

程序如下:

% 文件名:example2.m
% 时间:2020年9月29日
% 作者:乐观的lishan
% 功能:利用模拟退火算法计算有顺序约束的指派问题
clear,close,clc
warning off
tic

%数据的生成
M = 27;       %任务数 
N = 35;       %员工数,员工应数大于任务数
lb = 10;      %成本下限
ub = 35;      %成本上限

%利用随机数生成代价矩阵
d = unifrnd (lb, ub, M, N);  %生成一个服从均匀分布的矩阵,数值范围[lb,ub],矩阵大小M×N
d = ceil(d); %朝正无穷大取整
for i = 2:M
    d(i,1:i-1) = inf;
    d(M-i+1,N-(i-1)+1:N) = inf;
end

% 保存数据,可用于下一次用相同的数据实验
save data 

%运行一次后可将此行上面的所有代码注释,再次运行即可使用相同的数据
load data.mat  

%初始化决策变量和函数值,即模拟退火中的状态和内能
S0 = sort(randperm(N,M));    %初始化分配策略,决策变量,即状态
Sum = inf;                   %初始化能量,目标函数值
H = 20;                      %蒙特卡洛生成组合数次数
%生成初始解,用蒙特卡洛方法随机生成H种组合,并选择其中一个较好的指派组合
for j = 1:H   
     S = sort(randperm(N,M));
     temp = 0; 
     for i = 1:M 
         temp=temp+d(i,S(i)); 
     end 
     if temp < Sum 
         S0 = S;
         Sum = temp; 
     end 
end 
%记录蒙特卡洛选优结果
sum = Sum;  
s0 = S0;

%模拟退火算法的参数设置
e = 0.1^30; %终止温度
L = 5000;    %Markov链长度
alf = 0.999; %温度衰减因子
T = 1;       %起始温度
cnt_limit = 300;        %最优值连续保持不变的次数上限

all_people = 1:N;       %所有元素构成的集合
S0 = s0;
cnt = 0;

record_solve(1,:) = S0;
record_value(1,:) = 0;
for i = 1:M
    record_value = record_value + d(i,S0(i));
end

%退火过程 
for k=2:L 
    %产生新解 
    original_solve = S0; %保留原解
    remain_people = setdiff(all_people,S0);    %求当前解在所有元素构成的集合中的补集
    index1 = randperm(N-M,1); 
    index2 = randperm(M,1);
    add_num = remain_people(index1);   %随机添加一个新元素
    S0(index2) = [];                   %随机删除当前解的一个元素
    new_solve = sort([S0,add_num]);    %添加一个新元素并排序生成新解
    record_solve(k,:) = new_solve;
    %计算代价函数值,即当前时刻目标值(内能)减去前一时刻目标值(内能)
    d1 = 0;
    d2 = 0;
    for i = 1:M
        d1 = d1 + d(i,original_solve(i));        %交换前,目标函数值
        d2 = d2 + d(i,new_solve(i));             %交换后,目标函数值
    end
    df = d2-d1; %内能差
    record_value(k,:) = d2;
    
    %接受准则 
    if df<0  %内能降低,此解与上一解比较更优
        S0 = new_solve;   
        if d2 < Sum(k-1)
            bset_solve = new_solve;
            Sum(k) = d2;
        else
            Sum(k) = Sum(k-1);
        end
    else
        if exp(-df/T)>rand(1)  %内能增大,此解与上一解比较更差,以玻尔兹曼概率接受此解
            S0 = new_solve;      
        else    %内能增大,且不接受新解
            S0 = original_solve;
        end
        Sum(k) = Sum(k-1);
    end
    
    %判断最优值是否不再变动
    if Sum(k) == Sum(k-1)
        cnt = cnt + 1;
        if cnt >= cnt_limit
            break;
        end
    else
        cnt = 0;
    end
    
    %温度判断
    T=T*alf;  %降低温度,开始冷却
    if T<e   %温度小于临界值,算法提前结束
        break; 
    end 
end

toc

%结果验证
d3 = 0;
for i = 1:M
    d3 = d3 + d(i,bset_solve(i));     
end
if d3 == Sum(end)
    fprintf('结果验证【成功】\n')
else
    fprintf('结果验证【失败】\n')
end

best_plan = (record_solve((record_value == Sum(end)),:));
[b,location]= unique(best_plan,'rows','first');
res = sortrows([location,b]);
new_best_plan=res(:,2:size(res,2));
%输出结果
fprintf('完成指派任务的方案总数为:%.0f\n',nchoosek(N,M))
fprintf('程序迭代次数为:%.0f\n',length(Sum))
fprintf('完成指派任务所需的最少成本为:%.0f\n',Sum(end))
for i = 1:size(new_best_plan,1)
    fprintf('对应第%d种指派方案为:%s\n',i,num2str(new_best_plan(i,:)))
end

%绘制收敛坐标图
plot(Sum,'r','LineWidth',2)
%绘图说明
xlabel('收敛次数')
ylabel('成本值')
title(['收敛示意图:','第',num2str(length(Sum)),'次收敛'])

 程序运行结果如下:

记录两次较好的运行结果

 

更改任务数、员工总数,程序也能很快的完成收敛。当然模拟退火每次运行结果不一样,可以多运行几次,记录一个较好的结果,这样能够在短时间内得到一个较好解方案

通过遍历法可以验证模拟退火算法结果的有效性,其结果保持一致,但模拟退火算法可以更快得出结果

结论

本文对有顺序约束的任务指派问题进行了分析,有顺序约束即任务的完成有先后顺序之分,员工的选取也有先后顺序之分(只能向后选取),在这个限制条件下,本文先给出了遍历法的解决方法,并给出了Matlab代码和示例结果。然后提出在任务数和员工数增大的情况下遍历法不再适用,因为本质上这是一个离散的组合优化问题,由此引入了蒙特卡洛算法加模拟退火算法的解决思路,并给出了算法步骤和Matlab代码以及示例结果。本文的模拟退火算法与一般退火算法不同,由于本文的组合情况有顺序约束,常规退火算法中的扰动方法不再适用,本文使用方法为:在当前解中随机删除一个元素,再随机加入一个新元素的方法构造新解,最终取得了比较好的效果。

感谢博主“乐观的阿锡”乐观的阿锡的博客_CSDN博客-计算机,go语言,Linux系统编程学习笔记领域博主为本文提供了问题基础以及解决的思路

参考文献

[1] 李冰,徐杰,杜文.用模拟退火算法求解有顺序约束指派问题[J].系统工程理论方法应用,2002(04):330-335.

[2] 赵越.模拟退火算法求解指派问题新探[J].吉林建筑工程学院学报,2011,28(04):61-63.

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乐观的lishan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值