分枝限界课后作业作求解分配问题

有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
操作员138412
操作员2912135
操作员38793
操作员412768

问题的解向量是<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;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Gowi_fly

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

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

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

打赏作者

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

抵扣说明:

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

余额充值