【车间调度】基于k-insertion的禁忌搜索算法求解柔性作业车间调度问题(Java源码)

网上关于禁忌搜索的描述有很多。这里我就不进行赘述了。

禁忌搜索流程

说一下我自己实现禁忌搜索算法的流程。

  1. 生成初始解,并将初始解赋值给当前解和最优解
  2. 对当前解进行局部邻域搜索(搜索当前解的邻居)。搜索过程中,如果一个解存在于禁忌表中,不计算该解的函数值,最后会找到一个局部最优解(并不是找到比当前解更优的解,而是找到当前解的邻居中的最优解)。
  3. 将局部最优解赋值给当前解。
  4. (特赦准则)判断禁忌表中的某一个解是否优于当前解?若是,将当前解禁忌,将该解解禁赋给当前解,若否,直接将当前解禁忌(如果此时禁忌表达到禁忌长度,解禁第一个对象,将该解放入禁忌表尾部)
  5. 判断全局最优是否需要进行更新
  6. 循环上边过程,达到迭代次数之后退出。

本文进行邻域搜索的过程中,产生邻居解的方式是k-insertion。具体过程可以看以下文章:
【车间调度】论文阅读复现——effective neighbourhood functions for the flexible job shop problem

Java源码

代码的项目结构:
在这里插入图片描述

完整代码请进入仓库查看(如果觉得有用的话,留下一个star吧~):

https://gitee.com/xcy-ghl/tradition_fjsp.git

https://gitee.com/xcy-ghl/tradition_fjsp.git

这里只贴出主要代码(k-insertion和禁忌搜索):

/**
     * 根据插入信息,将v插入机器k中
     * @param insertInformation 插入信息,第一个元素为移除的关键节点的number,第二个元素为关键节点选择的机器k,第三个元素为在机器中选择的位置
     * @return 插入完毕的析取图
     */
    public DisjunctiveGraph kInsertion(DisjunctiveGraph graph, DataManager dataManager, int[] insertInformation) {

        List<DisjunctiveGraphNode> nodes = graph.getNodes();
        int vNumber=insertInformation[0];
        DisjunctiveGraphNode v = nodes.get(vNumber);
        int k=insertInformation[1];
        int insertIndex=insertInformation[2];
        int jobPreV=insertInformation[3];
        int jobNextV=insertInformation[4];
        List<Integer> qk = graph.getMachineAndNodeListMap().get(k);
        List<Integer> vMachineSeq =  graph.getMachineAndNodeListMap().get(v.getMachineNum());
        int vIndexInMachine = searchRemoveNodeInMachine(0, vMachineSeq.size() - 1, vMachineSeq, v.getNumber(), nodes);

        //在插入之前,需要先将v移除原本的机器
        if (vIndexInMachine != 0) {
            int machinePreV = vMachineSeq.get(vIndexInMachine - 1);
//            System.out.println("机器紧前工序移除前:"+nodes.get(machinePreV));
            if (machinePreV != jobPreV) {
                if (!nodes.get(machinePreV).getNextNodes().removeIf(nextNode -> nextNode == v.getNumber())) {
                    throw new IllegalStateException("not expecting to arrive here");
                }
                if (!v.getPreNodes().removeIf(preNode -> preNode == nodes.get(machinePreV).getNumber())) {
                    throw new IllegalStateException("not expecting to arrive here");
                }
            }
//            System.out.println("机器紧前工序移除后:"+nodes.get(machinePreV));
        }
        if (vIndexInMachine != vMachineSeq.size() - 1) {
            int machineNextV = vMachineSeq.get(vIndexInMachine + 1);
//            System.out.println("机器紧后工序移除前:"+nodes.get(machineNextV));
            if (machineNextV != jobNextV) {
                if (!nodes.get(machineNextV).getPreNodes().removeIf(preNode -> preNode == v.getNumber())) {
                    throw new IllegalStateException("not expecting to arrive here");
                }
                if (!v.getNextNodes().removeIf(nextNode -> nextNode == nodes.get(machineNextV).getNumber())) {
                    throw new IllegalStateException("not expecting to arrive here");
                }
            }
//            System.out.println("机器紧后工序移除后:"+nodes.get(machineNextV));
        }
        if (vIndexInMachine != 0 && vIndexInMachine != vMachineSeq.size() - 1) {
            DisjunctiveGraphNode machinePreV = nodes.get(vMachineSeq.get(vIndexInMachine - 1));
            DisjunctiveGraphNode machineNextV = nodes.get(vMachineSeq.get(vIndexInMachine + 1));
            if (machinePreV.getJobNum() != machineNextV.getJobNum() || machinePreV.getNumber() + 1 != machineNextV.getNumber()) {
                machinePreV.getNextNodes().add(machineNextV.getNumber());
                machineNextV.getPreNodes().add(machinePreV.getNumber());
            }
        }
        vMachineSeq.remove(vIndexInMachine);

        //将v插入k中选中的位置,断开insertIndex前后工序的机器弧,并重连v与机器紧前/紧后工序(若有且不是作业紧前紧后工序)机器弧,并重新设置机器、权重
        //断开
        qk.add(insertIndex, v.getNumber());
        if (insertIndex !=0&&insertIndex !=qk.size()-1) {
            DisjunctiveGraphNode machinePreNode = nodes.get(qk.get(insertIndex - 1));
            DisjunctiveGraphNode machineNextNode = nodes.get(qk.get(insertIndex + 1));
            if (machinePreNode.getJobNum() != machineNextNode.getJobNum() || machinePreNode.getNumber() + 1 != machineNextNode.getNumber()) {
                //不是相邻的同作业工序,断开它们之间的连接弧
                if (!machinePreNode.getNextNodes().removeIf(nextNode -> nodes.get(nextNode).getNumber() == machineNextNode.getNumber())) {
                    throw new IllegalStateException("not expecting to arrive here");
                }
                if (!machineNextNode.getPreNodes().removeIf(preNode -> nodes.get(preNode).getNumber() == machinePreNode.getNumber())) {
                    throw new IllegalStateException("not expecting to arrive here");
                }
            }
        }
        //重连
        if (insertIndex !=0) {
            //前一个工序存在,如果前一个工序跟当前工序不是相邻的同作业工序,进行连接
            DisjunctiveGraphNode machinePreNode = nodes.get(qk.get(insertIndex - 1));
            if (machinePreNode.getJobNum() != v.getJobNum() || machinePreNode.getNumber() + 1 != v.getNumber()) {
                machinePreNode.getNextNodes().add(v.getNumber());
                v.getPreNodes().add(machinePreNode.getNumber());
            }
        }
        if (insertIndex !=qk.size()-1) {
            //后一个工序存在,如果后一个工序跟当前工序不是相邻的同作业工序,进行连接
            DisjunctiveGraphNode machineNextNode = nodes.get(qk.get(insertIndex + 1));
            if (machineNextNode.getJobNum() != v.getJobNum() || machineNextNode.getNumber() - 1 != v.getNumber()) {
                machineNextNode.getPreNodes().add(v.getNumber());
                v.getNextNodes().add(machineNextNode.getNumber());
            }
        }
        v.setMachineNum(k);
        Integer newProcessTime = dataManager.getOperationProcessTimeMap().get(v.getOperationCode() + "-" + k);
        v.setProcessTime(newProcessTime);
        nodes.forEach(node->{
            node.setLatestPreNode(null);
            node.setStartTime(0);
            node.setTailTime(0);
        });
        //重新计算所有节点的头长度 尾长度、关键路径
        calStartTime(nodes.get(0), nodes, new boolean[nodes.size()], false);
        calTailTime(nodes.get(nodes.size() - 1), nodes, new boolean[nodes.size()], false);
        List<Integer> newCriticalPath = new ArrayList<>();
        getCriticalPath(nodes.get(nodes.size() - 1), nodes, newCriticalPath);
        graph.setInsertInfo(insertInformation);
        graph.setCriticalPath(newCriticalPath);
        graph.setMakespan(nodes.get(nodes.size() - 1).getStartTime());
//        nodes.forEach(System.out::println);
//        System.out.println(graph.getMakespan());
        return graph;
    }


    /**
     * 在析取图的关键路径上随机选择一个节点,将其从机器中移除,选择另一个机器k,选择一个位置插入
     * @return 数组,第一个元素为移除的关键节点的number,第二个元素为关键节点选择的机器k,第三个元素为在机器中选择的位置
     */
    public int[] getInsertInfo(DisjunctiveGraph graph, DataManager dataManager){

        List<DisjunctiveGraphNode> nodes = graph.getNodes();
        Map<Integer, List<Integer>> jobMap = graph.getJobAndNodeListMap();

        //随机选择关键路径上的一个节点
        int randomIndex = random.nextInt(graph.getCriticalPath().size());
        DisjunctiveGraphNode v = nodes.get(graph.getCriticalPath().get(randomIndex));
        List<Integer> vJobSeq = jobMap.get(v.getJobNum());
        int vIndexInJob = searchRemoveNodeInJob(0, vJobSeq.size() - 1, vJobSeq, v.getNumber());
        int jobPreV = vIndexInJob == 0 ? nodes.get(0).getNumber() : vJobSeq.get(vIndexInJob - 1);
        int jobNextV = vIndexInJob == vJobSeq.size() - 1 ? nodes.get(nodes.size() - 1).getNumber() : vJobSeq.get(vIndexInJob + 1);
        //工序v在子图中的头长度和尾长度此时为作业紧前工序的头长度+作业紧前工序处理时间,作业紧后工作的尾长度+作业紧后工序处理时间
        int vStartTime = nodes.get(jobPreV).getStartTime() + nodes.get(jobPreV).getProcessTime();
        int vTailTime = nodes.get(jobNextV).getTailTime() + nodes.get(jobNextV).getProcessTime();
        //重新为v随机选择一个机器k
        List<Integer> vOptionalMachines = dataManager.getOperationCodeAndMachineListMap().get(v.getOperationCode());
        Integer k = vOptionalMachines.get(random.nextInt(vOptionalMachines.size()));
        List<Integer> qk = new ArrayList<>(graph.getMachineAndNodeListMap().get(k));
        //v刚好选中了原来的机器,qk中要除去v
        qk.removeIf(nodeNumber->nodeNumber==v.getNumber());
        //用二分查找获得v在k中可插入的范围lk和rk
        int leftIndex = 0, rightIndex = qk.size();
        if (!qk.isEmpty() && vTailTime < nodes.get(qk.get(0)).getTailTime() + nodes.get(qk.get(0)).getProcessTime()) {
            leftIndex = searchLk(0, qk.size() - 1, qk, vTailTime, nodes);
        }
        if (!qk.isEmpty() && vStartTime < nodes.get(qk.get(qk.size() - 1)).getStartTime() + nodes.get(qk.get(qk.size() - 1)).getProcessTime()) {
            rightIndex = searchRk(0, qk.size() - 1, qk, vStartTime, nodes);
        }
        //选择v在k中插入的位置
        int insertIndex;
        if (rightIndex < leftIndex) {
            //此时lk和rk有交集,可插入的位置为[rightIndex,leftIndex]
            insertIndex = random.nextInt(leftIndex - rightIndex + 1) + rightIndex;
        } else {
            //此时lk和rk无交集,可插入的位置为[leftIndex,rightIndex]
            insertIndex = random.nextInt(rightIndex - leftIndex + 1) + leftIndex;
        }
        return new int[]{v.getNumber(),k,insertIndex,jobPreV,jobNextV};
    }

    /**
     * 在qk中寻找Lk最后一个工序的索引
     *
     * @return lk最后一个工序索引+1
     */
    private int searchLk(int start, int end, List<Integer> qk, int vTailTime, List<DisjunctiveGraphNode> nodes) {
        if (start == end) {
            int xTailTime = nodes.get(qk.get(start)).getTailTime();
            int xProcessTime = nodes.get(qk.get(start)).getProcessTime();
            if (vTailTime == xTailTime + xProcessTime) {
                //如果找到,lk的要求是tx+px>tv,所以lk最后一道工序的索引是start-1
                return start;
            } else {
                //如果没有找到,当前索引是target最近的左边,刚好是lk的最后一道工序
                return start + 1;
            }
        }
        int middle = (int) Math.ceil((start + end) / 2.0);
        if (vTailTime <= nodes.get(qk.get(middle)).getTailTime() + nodes.get(qk.get(middle)).getProcessTime()) {
            return searchLk(middle, end, qk, vTailTime, nodes);
        } else {
            return searchLk(start, middle - 1, qk, vTailTime, nodes);
        }
    }

    /**
     * 在k中寻找Rk第一个工序的索引
     */
    private int searchRk(int start, int end, List<Integer> qk, int vStartTime, List<DisjunctiveGraphNode> nodes) {
        if (start == end) {
            int xStartTime = nodes.get(qk.get(start)).getStartTime();
            int xProcessTime = nodes.get(qk.get(start)).getProcessTime();
            if (vStartTime == xStartTime + xProcessTime) {
                //如果找到,此时sx+px=sv,但是Rk的第一道工序要求sx+px>sv,所以第一道工序的索引是start+1
                return start + 1;
            } else {
                //没有找到,此时找到的索引是target最近的右边,刚好是Rk的第一道工序
                return start;
            }
        }
        int middle = (start + end) / 2;
        if (nodes.get(qk.get(middle)).getStartTime() + nodes.get(qk.get(middle)).getProcessTime() >= vStartTime) {
            return searchRk(start, middle, qk, vStartTime, nodes);
        } else {
            return searchRk(middle + 1, end, qk, vStartTime, nodes);
        }
    }

    /**
     * 在vJobSeq中找到target的索引
     */
    private int searchRemoveNodeInJob(int start, int end, List<Integer> vJobSeq, int target) {
        if (start == end) {

            if (vJobSeq.get(start) == target) {
                return start;
            } else {
                throw new IllegalStateException("not expecting to arrive here");
            }
        }
        int middle = (start + end) / 2;
        if (vJobSeq.get(middle) >= target) {
            return searchRemoveNodeInJob(start, middle, vJobSeq, target);
        } else {
            return searchRemoveNodeInJob(middle + 1, end, vJobSeq, target);
        }
    }

    /**
     * 在vMachineSeq中找到target的索引
     */
    private int searchRemoveNodeInMachine(int start, int end, List<Integer> vMachineSeq, int target, List<DisjunctiveGraphNode> nodes) {
        if (start == end) {
            if (vMachineSeq.get(start) == target) {
                return start;
            } else {
                throw new IllegalStateException("not expecting to arrive here");
            }
        }
        int middle = (start + end) / 2;
        if (nodes.get(vMachineSeq.get(middle)).getStartTime() >= nodes.get(target).getStartTime()) {
            return searchRemoveNodeInMachine(start, middle, vMachineSeq, target, nodes);
        } else {
            return searchRemoveNodeInMachine(middle + 1, end, vMachineSeq, target, nodes);
        }
    }


    /**
     * 根据可行调度计划生成析取图(遗传算法的调度计划结果转化为析取图,以方便进行禁忌搜索)
     */
    public DisjunctiveGraph convertScheduleToGraph(ScheduleResult scheduleResult, DataManager dataManager) {
        List<DisjunctiveGraphNode> nodes = new ArrayList<>();
        //先根据作业优先顺序生成有向图
        ArrayList<Job> jobs = scheduleResult.getJobs();
        int nodeNumber = 0;
        DisjunctiveGraphNode endNode = new DisjunctiveGraphNode("end", dataManager.getOperationNum() + 1, new ArrayList<>(), new ArrayList<>());
        endNode.setMachineNum(-1);
        endNode.setJobNum(-1);
        for (int i = 0; i < jobs.size(); i++) {
            if (i == 0) {
                //加入虚拟开始节点
                DisjunctiveGraphNode startNode = new DisjunctiveGraphNode("start", nodeNumber, new ArrayList<>(), new ArrayList<>());
                startNode.setMachineNum(-1);
                startNode.setJobNum(-1);
                nodes.add(startNode);
                nodeNumber++;
            }
            Job job = jobs.get(i);
            for (int j = 0; j < job.getOperations().size(); j++) {
                Operation operation = job.getOperations().get(j);
                DisjunctiveGraphNode node = new DisjunctiveGraphNode(operation.getCode(), nodeNumber, new ArrayList<>(), new ArrayList<>());
                node.setJobNum(operation.getJobNumber());
                node.setMachineNum(operation.getMachineNum());
                node.setProcessTime(operation.getProcessTime());
                if (j == 0) {
                    //作业的第一道工序的前驱节点必定是虚拟开始节点
                    node.getPreNodes().add(nodes.get(0).getNumber());
                    nodes.get(0).getNextNodes().add(node.getNumber());
                } else {
                    DisjunctiveGraphNode preNode = nodes.get(nodes.size() - 1);
                    preNode.getNextNodes().add(node.getNumber());
                    node.getPreNodes().add(preNode.getNumber());
                }
                if (j == job.getOperations().size() - 1) {
                    //作业的最后一道工序必定连向虚拟结束节点
                    node.getNextNodes().add(endNode.getNumber());
                    endNode.getPreNodes().add(node.getNumber());
                }
                nodes.add(node);
                nodeNumber++;
            }
        }
        nodes.add(endNode);
        //接着加入机器的析取弧
        for (Machine machine : scheduleResult.getMachines()) {
            for (int i = 0; i < machine.getOperations().size() - 1; i++) {
                Operation operation = machine.getOperations().get(i);
                Operation nextOperation = machine.getOperations().get(i + 1);
                //得到当前工序和下一个工序对应的节点
                DisjunctiveGraphNode node = nodes.get(dataManager.getOperationCodeAndIndexMap().get(operation.getCode()) + 1);
                DisjunctiveGraphNode nextNode = nodes.get(dataManager.getOperationCodeAndIndexMap().get(nextOperation.getCode()) + 1);
                //当前工序和下一个工序属于同一个作业的前后工序,不用添加机器弧
                if (node.getJobNum() != nextNode.getJobNum() || node.getNumber() + 1 != nextNode.getNumber()) {
                    node.getNextNodes().add(nextNode.getNumber());
                    nextNode.getPreNodes().add(node.getNumber());
                }
            }
        }
        //计算每个节点的头长度和尾长度
        calStartTime(nodes.get(0), nodes, new boolean[nodes.size()], false);
        calTailTime(nodes.get(nodes.size() - 1), nodes, new boolean[nodes.size()], false);
        //获得关键路径
        List<Integer> criticalPath = new ArrayList<>();
        getCriticalPath(nodes.get(nodes.size() - 1), nodes, criticalPath);
        //按照机器号对节点进行分组,分组之后的机器序列需要按照升序排序
        Map<Integer, List<Integer>> machineMap = nodes.stream().collect(Collectors.groupingBy(DisjunctiveGraphNode::getMachineNum,
                Collectors.mapping(DisjunctiveGraphNode::getNumber, Collectors.toList())));
        //析取图中的节点的机器并不全面,可能会漏掉一些机器,需要手动补上
        for (int i = 0; i < dataManager.getMachineNum(); i++) {
            if (!machineMap.containsKey(i)) {
                machineMap.put(i, new ArrayList<>());
            }
        }
        Map<Integer, List<Integer>> jobMap = nodes.stream().collect(Collectors.groupingBy(DisjunctiveGraphNode::getJobNum,
                Collectors.mapping(DisjunctiveGraphNode::getNumber, Collectors.toList())));
        jobMap.forEach((key, value) -> value.sort(Integer::compareTo));
        machineMap.forEach((key, value) -> value.sort(Comparator.comparingInt(o -> nodes.get(o).getStartTime())));
        return new DisjunctiveGraph(nodes, machineMap, jobMap, criticalPath, nodes.get(nodes.size() - 1).getStartTime(),new int[]{0});
    }


    /**
     * 计算析取图中每一个节点的头长度
     *
     * @param node    虚拟开始节点
     * @param visited 记录节点被访问的情况
     * @param back    该节点是否是前驱节点
     */
    private void calStartTime(DisjunctiveGraphNode node, List<DisjunctiveGraphNode> nodes, boolean[] visited, boolean back) {
        if (visited[node.getNumber()]) {
            //该节点被访问过,返回
            return;
        }
        //访问所有未被访问过的前驱节点,并记录前驱节点之中的最迟完工时间
        for (Integer preNode : node.getPreNodes()) {
            if (!visited[preNode]) {
                calStartTime(nodes.get(preNode), nodes, visited, true);
            }
            int startTime = nodes.get(preNode).getStartTime() + nodes.get(preNode).getProcessTime();
            if (startTime >= node.getStartTime()) {
                node.setStartTime(startTime);
                node.setLatestPreNode(preNode);
            }
        }
        //所有前驱节点被访问完毕,标记该节点被访问过
        visited[node.getNumber()] = true;
        //如果此时是前驱节点的搜索过程,返回到原本的节点
        if (back) {
            return;
        }
        //访问该节点的所有未被访问过的后继节点
        for (Integer nextNode : node.getNextNodes()) {
            if (!visited[nextNode]) {
                calStartTime(nodes.get(nextNode), nodes, visited, false);
            }
        }
    }

    /**
     * 计算析取图中每一个节点的尾长度
     *
     * @param node    虚拟结束节点
     * @param visited 记录节点是否被访问过
     * @param forward 该节点是否是后继节点
     */
    private void calTailTime(DisjunctiveGraphNode node, List<DisjunctiveGraphNode> nodes, boolean[] visited, boolean forward) {
        if (visited[node.getNumber()]) {
            //该节点被访问过,返回
            return;
        }
        //访问所有未被访问过的后继节点,并记录后继节点之中的最大尾长度
        for (Integer nextNode : node.getNextNodes()) {
            if (!visited[nextNode]) {
                calTailTime(nodes.get(nextNode), nodes, visited, true);
            }
            node.setTailTime(Math.max(node.getTailTime(), nodes.get(nextNode).getTailTime() + nodes.get(nextNode).getProcessTime()));
        }
        //所有后继节点被访问完毕,标记该节点被访问过
        visited[node.getNumber()] = true;
        //如果此时是后继节点的搜索过程,返回到原本的节点
        if (forward) {
            return;
        }
        //访问该节点的所有未被访问过的前驱节点
        for (Integer preNode : node.getPreNodes()) {
            if (!visited[preNode]) {
                calTailTime(nodes.get(preNode), nodes, visited, false);
            }
        }
    }

    /**
     * 获得析取图的关键路径
     *
     * @param node         第一次输入需要是析取图的虚拟结束节点
     * @param criticalPath 传入一个空的列表,该方法执行完毕后列表会按开始时间为序填充关键节点
     */
    private void getCriticalPath(DisjunctiveGraphNode node, List<DisjunctiveGraphNode> nodes, List<Integer> criticalPath) {
        if (null == node.getLatestPreNode()) {
            //到达头节点,为了之后方便,开始节点不要添加入关键路径
            return;
        }
        //进入上一个节点
        getCriticalPath(nodes.get(node.getLatestPreNode()), nodes, criticalPath);
        if (!"end".equals(node.getOperationCode())) {
            //为了之后方便,结束节点不要添加入关键路径
            criticalPath.add(node.getNumber());
        }
    }

实现效果及可视化

参数设置为:禁忌表长50,迭代5000,局部搜索次数70,最大允许无更优解代数500
使用测试数据BRData进行测试:

算例名称计算结果(makespan)计算时间(ms)
MK0140557
MK0228796
MK03204851
MK0464980
MK05183881
MK06632468
MK07166993
MK085371835
MK093115001
MK1021210194

可视化代码请见我的另一篇文章:
【车间调度】车间调度甘特图可视化(Java源码)

算例MK10的可视化效果:
在这里插入图片描述

看了一下其他文献,有一些算例的最优解没有求到,可能跟参数设置、计算时间和实现方式(我没加特赦准则)有关。k-insertion有一种计算近似关键路径长度来将时间复杂度从 O ( N ) O(N) O(N)降到 O ( l o g N ) O(logN) O(logN)的方式我也没有应用,所以这个算法还是比较慢的(特别是之后跟遗传算法结合,禁忌搜索一次就要几百毫秒实在是太慢了)。之后有空再优化一下。

  • 28
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值