Taillard等将对NEH启发式算法进行了改进,将时间复杂度从 O(mn^3) 降至了O(mn^2) ,详细原理见我的上一篇文章:
对Taillard提出的改进NEH算法进行了复现,c++源代码如下:
NEH.h:
#pragma once
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
class NEH
{
private:
int n; //作业数量
int m; //机器数量
vector<pair<int, double>> sumPcsTime; //作业根据总加工时间降序排列,int为作业,double为总加工时间
vector<int> res; //NEH算法求解的调度结果
double makespan = 0; //NEH算法调度结果对应的最大完工时间
//vector<vector<double>> e; //NEH中的e矩阵,e[i][j]为第i个作业在第j台机器上的完成时间
//vector<vector<double>> q; //NEH中的q矩阵,q[i][j]为第i个作业在第j台机器上开始的时间到所有操作结束的时间
//vector<vector<double>> f; //NEH中的f矩阵,f[i][j]为插入第i个位置的作业k在第j台机器上的最早相对完成时间
public:
NEH(const vector<vector<double>>& matrixT); //输入:加工时间矩阵T (从0开始,T[0][0]即作业1在机器1上加工时间)
vector<int> getRes(); //输出NEH算法求解结果
double getMakespan(); //输出NEH算法调度结果对应的最大完工时间
vector<vector<double>> calculateMakespan(vector<int> res, const vector<vector<double>>& matrixT); //直接计算最大完工时间的函数
//测试用
void printPcsTime(); //测试用,打印sumPcsTime
};
NEH.cpp:
#include "NEH.h"
#include <numeric>
using namespace std;
NEH::NEH(const vector<vector<double>>& matrixT) {
//初始化 作业数量n,机器数量m
n = matrixT.size();
m = matrixT[0].size();
sumPcsTime.resize(n);
//测试用,打印作业数n、机器数m、加工时间矩阵matrixT
cout << "--NEH算法输入--" << endl;
cout << "作业数:" << n << " 机器数:" << m << endl << "加工时间:" << endl;
for (size_t i = 0; i < matrixT.size(); i++)
{
for (size_t j = 0; j < matrixT[0].size(); j++)
{
cout << matrixT[i][j] << " ";
}
cout << endl;
}
cout << endl;
//NEH Step1:作业根据总加工时间降序排列
//1.1 保存作业在各机器上的加工时间之和
for (int i = 0; i < sumPcsTime.size(); i++) {
sumPcsTime[i].first = i + 1;
sumPcsTime[i].second = accumulate(matrixT[i].begin(), matrixT[i].end(), 0.0); //0 -- int;0.0 -- double
}
//测试用,打印排序前作业顺序以及对应的总加工时间
cout << "NEH Step1 -- 作业根据总加工时间降序排列" << endl;
cout << "排序前:" << endl;
printPcsTime();
//1.2 快速排序 O(nlogn)
sort(sumPcsTime.begin(), sumPcsTime.end(), [](auto a, auto b) {
return a.second > b.second;
});
//测试用,打印排序前作业顺序以及对应的总加工时间
cout << "排序后:" << endl;
printPcsTime();
cout << endl;
//NEH Step2:对排序后前两个作业进行调度
//2.1 分别计算两种排列{sumPcsTime[0].first, sumPcsTime[1].first}、{sumPcsTime[1].first, sumPcsTime[0].first}的完工时间
double makespan_1 = calculateMakespan(vector<int>{sumPcsTime[0].first, sumPcsTime[1].first}, matrixT)[1][m - 1];
double makespan_2 = calculateMakespan(vector<int>{sumPcsTime[1].first, sumPcsTime[0].first}, matrixT)[1][m - 1];
//2.2 返回完工时间更小的调度结果
if (makespan_1 < makespan_2) {
res.push_back(sumPcsTime[0].first);
res.push_back(sumPcsTime[1].first);
}
else {
res.push_back(sumPcsTime[1].first);
res.push_back(sumPcsTime[0].first);
}
//测试用,打印两个调度结果
cout << "NEH Step2 -- 对排序后前两个作业进行调度" << endl;
vector<vector<double>> schedule1 = calculateMakespan(vector<int>{sumPcsTime[0].first, sumPcsTime[1].first}, matrixT);
vector<vector<double>> schedule2 = calculateMakespan(vector<int>{sumPcsTime[1].first, sumPcsTime[0].first}, matrixT);
cout << "第一种排列:" << sumPcsTime[0].first << ", " << sumPcsTime[1].first << endl << "对应调度矩阵:";
for (size_t i = 0; i < schedule1.size(); i++)
{
for (size_t j = 0; j < schedule1[0].size(); j++)
{
cout << schedule1[i][j] << " ";
}
cout << "; ";
}
cout << "对应完工时间:" << schedule1[1][m - 1] << endl;
cout << "第二种排列:" << sumPcsTime[1].first << ", " << sumPcsTime[0].first << endl << "对应调度矩阵:";
for (size_t i = 0; i < schedule2.size(); i++)
{
for (size_t j = 0; j < schedule2[0].size(); j++)
{
cout << schedule2[i][j] << " ";
}
cout << "; ";
}
cout << "对应完工时间:" << schedule2[1][m - 1] << endl;
cout << "NEH Step2 调度结果:" << res[0] << " " << res[1] << endl << endl;
//测试用
cout << "NEH Step3 -- k从3到n,计算使完工时间最小的第k个作业的最优插入位置" << endl;
//NEH Step3:k从3到n,计算使完工时间最小的第k个作业的最优插入位置
for (int k = 3; k <= n; k++) {
//初始化e, q, k, Mmin, Imin;为了编程方便(对齐下标),扩充了q和f的大小
vector<vector<double>> e(k, vector<double>(m + 1, 0)); //NEH中的e矩阵
vector<vector<double>> q(k + 1, vector<double>(m + 2, 0)); //NEH中的q矩阵
vector<vector<double>> f(k + 1, vector<double>(m + 1, 0)); //NEH中的f矩阵
//3.1 计算第i个作业在第j台机器上的最早完成时间e[i][j]
for (int i = 1; i <= k - 1; i++) {
for (int j = 1; j <= m; j++) {
e[i][j] = max(e[i][j - 1], e[i - 1][j]) + matrixT[res[i - 1] - 1][j - 1];
}
}
//3.2 计算第i个作业在第j台机器上开始的时间到操作结束的时间q[i][j]
for (int i = k - 1; i >= 1; i--) {
for (int j = m; j >= 1; j--) {
q[i][j] = max(q[i][j + 1], q[i + 1][j]) + matrixT[res[i - 1] - 1][j - 1];
}
}
//3.3 计算插入第i个位置的作业k在第j台机器上的最早相对完成时间f[i][j]
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= m; j++) {
f[i][j] = max(f[i][j - 1], e[i - 1][j]) + matrixT[sumPcsTime[k - 1].first - 1][j - 1];
}
}
//3.4 计算在第i个位置插入作业k时,部分完成时间Mi的值,并更新Mmin、Imin
double Mmin = DBL_MAX; //用于记录第k个作业插入不同位置时的最小完工时间
int Imin = 0; //用于记录第k个作业插入取得最小完工时间时对应的插入位置
vector<double> recordMi; //测试用,记录Mi
for (int i = 1; i <= k; i++) {
double Mi = 0;
//计算Mi
for (int j = 1; j <= m; j++) {
if (f[i][j] + q[i][j] > Mi) Mi = f[i][j] + q[i][j];
}
//比较,得出最优插入位置Imin,以及对应的Mmin
if (Mi < Mmin) {
Mmin = Mi;
Imin = i;
}
//测试用,记录Mi
recordMi.push_back(Mi);
}
//在最优位置插入作业k,更新调度结果
res.insert(res.begin() + (Imin - 1), sumPcsTime[k - 1].first);
if (k == n) makespan = Mmin;
//测试用
cout << "****插入第" << k << "个作业 ( 作业 " << sumPcsTime[k - 1].first << " )****";
cout << endl << "作业k插入位置i的完工时间Mi:";
for (size_t i = 0; i < recordMi.size(); i++) {
cout << recordMi[i] << " ";
}
cout << endl << "最小完工时间:" << Mmin << ", 对应插入位置:" << Imin << endl << "本次调度结果:";
for (size_t i = 0; i < res.size(); i++) {
cout << res[i] << " ";
}
cout << endl << endl;
}
//测试用,打印最终调度结果
cout << "NEH算法生成的调度结果:" << endl;
for (size_t i = 0; i < res.size(); i++)
{
cout << res[i] << " ";
}
cout << endl << "对应最大完工时间:" << makespan << endl;
}
vector<int> NEH::getRes() {
return res;
}
double NEH::getMakespan() {
return makespan;
}
vector<vector<double>> NEH::calculateMakespan(vector<int> res, const vector<vector<double>>& matrixT) {
int n = res.size(), m = matrixT[0].size();
vector<vector<double>> schedule(n, vector<double>(m, 0)); //调度矩阵,存放各机器上的调度结果
// 计算第1个工件:sche(J1,1) = matrixT(J1,1); sche(J1,k) = sche(J1,k-1) + matrixT(J1,k)
schedule[0][0] = matrixT[res[0] - 1][0];
for (int j = 1; j < m; j++)
{
schedule[0][j] = schedule[0][j - 1] + matrixT[res[0] - 1][j];
}
/* 计算第2至n个工件:sche(Ji,1) = sche(Ji-1,1) + matrixT(Ji,1);
sche(Ji,k) = max{sche(Ji-1,k), sche(Ji,k-1)} + matrixT(J1,k) */
for (int i = 1; i < n; i++)
{
schedule[i][0] = schedule[i - 1][0] + matrixT[res[i] - 1][0];
for (int j = 1; j < m; j++)
{
schedule[i][j] = max(schedule[i - 1][j], schedule[i][j - 1]) + matrixT[res[i] - 1][j];
}
}
// 最大完工时间:schedule[n - 1][m - 1]
return schedule;
}
//测试用
void NEH::printPcsTime() {
cout << "作业顺序:";
for (size_t i = 0; i < sumPcsTime.size(); i++)
{
cout << sumPcsTime[i].first << " ";
}
cout << endl << "总加工时间:";
for (size_t i = 0; i < sumPcsTime.size(); i++)
{
cout << sumPcsTime[i].second << " ";
}
cout << endl;
}
main.cpp:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include "NEH.h"
using namespace std;
//测试NEH
void testNEH() {
//vector<vector<double>> MatrixT(20, vector<double>(5, 0));
//Taillard-PFSP测试案例:ta001 已知最优解:1278
//对应排序: { 3,17,15,8,9,6,5,14,16,7,11,13,18,19,1,4,2,10,20,12 }
//MatrixT[0] = { 54, 79, 16, 66, 58 };
//MatrixT[1] = { 83, 3, 89, 58, 56 };
//MatrixT[2] = { 15, 11, 49, 31, 20 };
//MatrixT[3] = { 71, 99, 15, 68, 85 };
//MatrixT[4] = { 77, 56, 89, 78, 53 };
//MatrixT[5] = { 36, 70, 45, 91, 35 };
//MatrixT[6] = { 53, 99, 60, 13, 53 };
//MatrixT[7] = { 38, 60, 23, 59, 41 };
//MatrixT[8] = { 27, 5, 57, 49, 69 };
//MatrixT[9] = { 87, 56, 64, 85, 13 };
//MatrixT[10] = { 76, 3, 7, 85, 86 };
//MatrixT[11] = { 91, 61, 1, 9, 72 };
//MatrixT[12] = { 14, 73, 63, 39, 8 };
//MatrixT[13] = { 29, 75, 41, 41, 49 };
//MatrixT[14] = { 12 ,47 ,63 ,56 ,47 };
//MatrixT[15] = { 77 ,14 ,47 ,40 ,87 };
//MatrixT[16] = { 32 ,21 ,26 ,54 ,58 };
//MatrixT[17] = { 87 ,86 ,75 ,77 ,18 };
//MatrixT[18] = { 68 , 5 ,77 ,51 ,68 };
//MatrixT[19] = { 94 ,77 ,40 ,31 ,28 };
vector<vector<double>> MatrixT = { {19,46,65},{44,63,12},{85,56,98},{59,68,25},{87,66,53},{51,4,63} };
NEH neh(MatrixT);
cout << "直接计算得出的最大完工时间:"
<< neh.calculateMakespan(neh.getRes(), MatrixT)[MatrixT.size() - 1][MatrixT[0].size() - 1] << endl;
}
int main()
{
srand((unsigned)time(NULL));
testNEH();
system("pause");
}
使用的测试案例来自:
(19条消息) 基础NEH算法 在 流水车间调度问题(FlowShop)问题 的应用(C++)_Liablbility的博客-博客_neh算法
测试结果:
可以看到调度结果为{ 6, 2, 1, 3, 5, 4 },最大完工时间为445,与上述链接一致。
main.cpp中还包含了Taillard的一个测试案例(ta001),可自行测试。