有n个操作员,n项作业,已知第i项作业分配给第j个操作员的加工时间为tj,i,i,j=1,2,…,n。如果所有操作员都从时刻0开始工作,最后一项作业完成的时刻记作全部作业的完成时间。问如何分配作业使得全部作业的完成时间最短?下表是一个n=4的实例,其中第j行的4个数据分别表示第j位操作员完成4项作业所需时间,j=1, 2, 3, 4。
作业1 | 作业2 | 作业3 | 作业4 | |
---|---|---|---|---|
操作员1 | 3 | 8 | 4 | 12 |
操作员2 | 9 | 12 | 13 | 5 |
操作员3 | 8 | 7 | 9 | 3 |
操作员4 | 12 | 7 | 6 | 8 |
问题的解向量是<x1,x2,…,xn >,其中xi=j表示第i项作业分配给操作者j,i,j={1,2,…,n}。搜索树是一颗n叉树。请使用最小优先队列分支界限法,编程得到结果。
#include<iostream>
#include<queue>
using namespace std;
#define NUM_WORK 4 //工人数或作业数
//结点信息
typedef struct Node
{
int depth;//结点深度,代表已经分配完几项作业
int time;//当前节点分配方案已有的时间总和
int down;//根据当前分配方案或路径,最终可能的最小时间
string sleep_worker; //还没有分配的工人编号符号串,"13"代表1、3号工人还没工作
string worker;//当前已分配的工人编号,“321”之类的表示3、2、1号工人分别做第1,2,3项工作
Node *parent;//指向父节点指针
} Node;
//down下界小的节点排前面
struct cmp
{
bool operator()(Node *a, Node *b)
{
return a->down > b->down;
}
};
//全局变量
int Worker_Time[
NUM_WORK + 1][NUM_WORK + 1] = {{0, 0, 0, 0, 0},
{0, 3, 8, 4, 12},
{0, 9, 12, 13, 5},
{0, 8, 7, 9, 3},
{0, 12, 7, 6, 8}}; //操作员每项作业的工作时间,为书写程序方便,下标为0的列或行的不要了
/*
格式:
work1 work2 //作业
person1 2 3 //用时/h
person2 3 2
保存为这种数组,行0,列0都不要
0 0 0
0 2 3
0 3 2
*/
int Max_Time; //上界,最多时间限制
int Min_Time; //下界
Node *minTime_Node = nullptr; //最优节点
//功能:初始化操作员时间表
void initWorkers()
{
//初始化最多时间,即上界,实际上这道题要突破的是下界
for (int i = 1; i <= NUM_WORK; i++)
{
int time = Worker_Time[1][i];
for (int j = 2; j <= NUM_WORK; j++)
{
if (Worker_Time[j][i] > time) //单纯让每个作业最大时间相加
time = Worker_Time[j][i];
}
Max_Time += time;
}
//一些解释,怕某些人看不懂
//cout << "\n在过程中遇到的像 13 数字串代表 1号作业1号工人做,2号作业3号工人做\n";
//cout << "以此类推,我是一项一项作业分配的,而分配每项作业时也会因已有的分配方案选择不同工人,从而产生长度相同但不一样的数字串\n" << endl;
}
//功能:获取采用当前节点分配方案后,分配完作业后最可能的最小时间,即下界
int getBound(Node *node)
{
int down = node->time; //先把已有的时间加上
string sleep_worker = node->sleep_worker; //还没分配的工人编号
if (node->depth < NUM_WORK) //非叶节点
{
/*
贪心法求下界
假如还有第3、4项作业没分。1,3号工人也没分,即当前节点深度为2,sleep_worker = "13"
那么先找出第3号作业在工人1,3中的最小时间time,然后down += time,如果3号工人最快,然后从未分配工人编号字符串中剔除3变为"1"
然后找第4号作业,在剩余工人中的最下值,此时只有1号工人了,down加上相应时间,此时下界就算完了
*/
for (int i = node->depth + 1; i <= NUM_WORK; i++) //i代表第几项作业
{
int fast_worker_id = sleep_worker.at(0) - '0'; //工人编号,先暂时选取串中第一个工人
int time = Worker_Time[fast_worker_id][i]; //最小时间
//然后在sleep_worker中找找剩余工人中有没有更快的
for (int j = 1; j < sleep_worker.length(); j++)
{
int worker_id = sleep_worker.at(j) - '0';
if (Worker_Time[worker_id][i] < time) //如果时间更小
{
time = Worker_Time[worker_id][i];
fast_worker_id = worker_id;
}
}
down = max(time,down); //下界加上这个最小时间
char index = fast_worker_id + '0';
sleep_worker.erase(sleep_worker.find(index), 1); //从sleep_worker中剔除该工人编号
}
}
else//为叶节点
{
down = node->time; //直接等于已有时间
if (down < Max_Time)//更新上界,最大时间
Max_Time = down;
minTime_Node = node;//设为目前最优节点
}
string str = node->worker;
cout << str << endl; //打印当前节点分配方案
return down;
}
//功能:分支限界算法
void branchAndBound()
{
Node *root = new Node(); //根节点
root->time = 0;
root->depth = 0;
root->worker = ""; //当前分配方案空
string sleep_worker = ""; //初始化未分配工人编号串
for (int i = 1; i <= NUM_WORK; i++)
{
sleep_worker += i + '0';
}
root->sleep_worker = sleep_worker;
root->down = getBound(root); //获取下界
minTime_Node = root;//用根节点初始化最小值节点指针,避免while循环中的判断出错
priority_queue<Node *, vector<Node *>, cmp> pt; //最小优先队列
pt.push(root);//根节点入队
while (!pt.empty())
{
Node *currentNode, *Child; //子节点没有啥左右之分
currentNode = pt.top();//取队首元素
pt.pop();//出队
cout << "分配第" << currentNode->depth + 1 << "项作业:" << endl; //当前深度
//分配当前作业,给父节点sleep_worker中的工人,尝试分别分配给其中一人,每次分配都是一样的,所以做成循环
for (int i = 0; i < currentNode->sleep_worker.length(); i++)
{
Child = new Node();
Child->depth = currentNode->depth + 1;
Child->time = max(currentNode->time, Worker_Time[currentNode->sleep_worker.at(i) - '0'][Child->depth]);
Child->worker = currentNode->worker + currentNode->sleep_worker.at(i); //分配方案串中加上对应的工人编号
sleep_worker = currentNode->sleep_worker; //先取父节点的串
sleep_worker.erase(i, 1); //抹去相应的工人编号
Child->sleep_worker = sleep_worker;
Child->down = getBound(Child);
Child->parent = currentNode;
if (Child->down <= Max_Time) //下界比最大时间大的没必要进队
{
if (Child->depth == NUM_WORK) //已找到最优节点
return; //结束
pt.push(Child);
}
else //丢弃本方案节点
{
string str = "分配节点:( " + Child->worker + " )丢弃";
cout << str << endl;
}
}
}
}
//功能:输出结果
void printResult()
{
cout << "\n解向量为: (";
for (int i = 0; i < minTime_Node->worker.length(); i++)
{
if (i > 0)
cout << ", ";
cout << minTime_Node->worker.at(i);
}
cout << ")" << endl;
string str = "";
for (int i = 0; i < minTime_Node->worker.length(); i++)
{
str += (i + 1) + '0';
str += "号作业分配给 ";
str += minTime_Node->worker.at(i);
str += "号工人\n";
}
cout << str << endl;
cout << "所用时间: " << minTime_Node->time << endl;
}
int main()
{
initWorkers();
branchAndBound();
printResult();
return 0;
}