Java实现最佳调度算法的回溯法实现

题目:
假设有n个任务由k个可并行工作的机器完成,完成任务i需要的时间为ti,试设计一个算法找出完成这n个任务的最佳调度,使得完成全部任务的时间最早。
 

之前算法课设的时候遇到的题目,发现市面上大部分的博客都是采用的C语言书写的这题,即使有解决方案,大多也是复制粘贴根本没有把思路讲清楚,所以我翻了翻之前的代码,努力把自己的思路讲清楚,包括详细步骤的描述,解空间树的绘制,也算是给自己的算法课设做个总结。

 

解题思路:
每一个任务都有K个选择,解空间树也就是一颗深度为K的满K叉树,首先我们选择一条路径往下走,以DFS(深度优先遍历)的当时来搜索整个解空间。每次搜索结束都记录下bestTime和与之对应的路径。一开始我们需要完整的走完一条路径,后面再进行遍历的时候只需要与现有的数据进行比较,当我们发现当前累积的时间已经超过当前的最优解时便停止遍历,即进行剪枝操作。

 

开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处向纵深方向移至一个新结点,并成为一个新的活结点,也成为当前扩展结点。 如果在当前的扩展结点处不能再向纵深方向扩展,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点;直至找到一个解或全部解。

 
详细的步骤说明:
1.将所有的任务都放置机器一中,不考虑其他机器,这也是我们深度优先遍历的第一步,将这个作为初始值,此时的bestTime为50,最佳调度路径为:1 1 1 1 1 1 1。

2.最后一个任务我们在分配的时候有三种选择,即机器一、二、三。

我们此时执行结束的代码为:

if(dep == numberOfTask){ // 达到了叶子节点
            int maxTime = compute(); // 计算完整调度的完成时间
            if(maxTime < best){ // 当前的时间比最优解还要少
                best = maxTime; // 更新时间
                for(int i = 0; i < numberOfTask; i++){
                    bestx[i] = path[i]; // 更新路径
                }
            }
            return;
        }
因为已经记录下了最佳时间,我们return回去,回到上一级中的这一段代码:

        // 尚未到达叶子节点,向纵深方向进行拓展
        for(int i = 0; i < numberOfMachine; i++){ // 把任务加到第i个机器里面去
            len[i] += timeOfTask[dep];
            path[dep] = i+1; // 记录下当前的临时路径
            if(len[i] < best){ // 如果已经大于了当前的最优时间,就不用继续了,放弃这一个任务
                backtrack(dep+1);
            }
            len[i] -= timeOfTask[dep]; // 回溯 减去这一任务的值,恢复这一机器的x【i】,证明这一机器不要这一任务,就去找其他机器。
        }
此时,我们的前(n-1)个任务都已经分配在了第一个机器中,return操作执行之前,第n个任务我们分配在了第一个机器,所得到的最佳时间为50,此时我们回溯到了上一步,第n个任务面临三种选择,我们将执行第二个选择,也就是第二台机器。

这时,我们留意到,此刻的最佳时间已经变成了47,这就执行了更新操作,我们再次执行return操作回到上一步。

3.由于之前我们将任务n分配给了机器一和二,这时我们的for循环将把任务n分配给机器三。

此刻的时间还是47,不需要执行更新操作,然后我们回到第(n-1)个节点,它也将面临三个选择。

4.将第(n-1)个节点分配给机器二

然后我们发现时间再次缩短为了45,更新时间和最佳路径。由于任务(n-1)分配给了机器二,尚未到达叶子节点,所以还需执行步骤1、2、3将任务n分配给机器一、二、三,进行时间的判断操作,如此反复即可。

 

核心代码:
    /**
     * 回溯法解决问题
     * 当dep>n时,算法搜索至叶子结点,得到一个新的作业调度方案。此时算法适时更新当前最优值和相应的当前最佳调度。
     * 当dep<n时,若当前扩展结点位于排列树的第(n-1)层,此时算法选择下一个要安排的作业进行搜索且向第(n-2)层回溯,
     * 以深度优先方式递归的对相应的子树进行搜索,对不满足上界约束的结点,则剪去相应的子树向第(n-2)层回溯。
     * @param dep
     */
    private void backtrack(int dep) {
        if(dep == numberOfTask){ // 达到了叶子节点
            int maxTime = compute(); // 计算完整调度的完成时间
            if(maxTime < best){ // 当前的时间比最优解还要少
                best = maxTime; // 更新时间
                for(int i = 0; i < numberOfTask; i++){
                    bestx[i] = path[i]; // 更新路径
                }
            }
            return;
        }
 
        // 尚未到达叶子节点,向纵深方向进行拓展
        for(int i = 0; i < numberOfMachine; i++){ // 把任务加到第i个机器里面去
            len[i] += timeOfTask[dep];
            path[dep] = i+1; // 记录下当前的临时路径
            if(len[i] < best){ // 如果已经大于了当前的最优时间,就不用继续了,放弃这一个任务
                backtrack(dep+1);
            }
            len[i] -= timeOfTask[dep]; // 回溯 减去这一任务的值,恢复这一机器的x【i】,证明这一机器不要这一任务,就去找其他机器。
        }
    }
没什么好说的,代码里面注释很详细,主要使用了回溯法以及剪枝的操作。

 

完整代码:
/**
 * @ Author     :heywecome
 * @ Date       :Created in 14:24 2018/12/23
 * @Version: $version$
 */
public class MachineWork {
    // 假设有n个任务由k个可并行工作的机器完成。完成任务i需要的时间为ti。
    // 试设计一个算法找出完成这n个任务的最佳调度,使得完成全部任务的时间最早
 
    int numberOfTask; //任务数
    int numberOfMachine; //机器数
    int best; // 记录最优的时间
    int[] timeOfTask; //每个任务所需的时间序列
    int[] len; // 每台机器所需时间序列
    int[] path; // 当前路径
    int[] bestx; // 最优调度:其中bestx[i]=m表示把第i项任务分配给第m台机器
 
    /**
     * 思路:回溯法 剪枝
     * 一个k个可并行的机器,实际就相当于把这k个机器当做容器,将时间填入容器中去,按照时间来搜索,
     * 优化:
     * 最优化剪枝:如果任何一个机器超过已知最优解则停止向下搜索
     * 排序:从大到小排序任务时间可以减少搜索时间
     */
 
    public MachineWork(int numberOfTask, int numberOfMachine, int[] timeOfTask){
        this.numberOfTask = numberOfTask;
        this.numberOfMachine = numberOfMachine;
        this.timeOfTask = timeOfTask;
    }
 
    /**
     * 初始化必要参数
     */
    private void init(){
        len = new int[numberOfMachine]; // 记录每个机器已经安排的时间
        best = Integer.MAX_VALUE;
        bestx = new int[numberOfTask];
        path = new int[numberOfTask];
    }
 
    /**
     * 打印最后的结果
     */
    private void printSolution(){
        Long startTime = System.nanoTime();
        backtrack(0);
        Long endTime = System.nanoTime();
        System.out.println("总共消耗的时间为: " + (endTime - startTime) + " ns");
 
        System.out.print("最优时间为:");
        System.out.println(best);
 
        System.out.println("每个任务消耗的时间为:");
        for (int i=0;i<numberOfTask;i++){
            System.out.print(timeOfTask[i]+"  ");
        }
        System.out.println();
 
        System.out.println("具体的规划如下:");
        for (int i=0;i<numberOfTask;i++){
            System.out.print(bestx[i]+"  ");
        }
    }
 
    public void findTheSolution(){
        init();
        printSolution();
    }
 
    /**
     * 回溯法解决问题
     * 当dep>n时,算法搜索至叶子结点,得到一个新的作业调度方案。此时算法适时更新当前最优值和相应的当前最佳调度。
     * 当dep<n时,若当前扩展结点位于排列树的第(n-1)层,此时算法选择下一个要安排的作业进行搜索且向第(n-2)层回溯,
     * 以深度优先方式递归的对相应的子树进行搜索,对不满足上界约束的结点,则剪去相应的子树向第(n-2)层回溯。
     * @param dep
     */
    private void backtrack(int dep) {
        if(dep == numberOfTask){ // 达到了叶子节点
            int maxTime = compute(); // 计算完整调度的完成时间
            if(maxTime < best){ // 当前的时间比最优解还要少
                best = maxTime; // 更新时间
                for(int i = 0; i < numberOfTask; i++){
                    bestx[i] = path[i]; // 更新路径
                }
            }
            return;
        }
 
        // 尚未到达叶子节点,向纵深方向进行拓展
        for(int i = 0; i < numberOfMachine; i++){ // 把任务加到第i个机器里面去
            len[i] += timeOfTask[dep];
            path[dep] = i+1; // 记录下当前的临时路径
            if(len[i] < best){ // 如果已经大于了当前的最优时间,就不用继续了,放弃这一个任务
                backtrack(dep+1);
            }
            len[i] -= timeOfTask[dep]; // 回溯 减去这一任务的值,恢复这一机器的x【i】,证明这一机器不要这一任务,就去找其他机器。
        }
    }
 
    // 计算一个完整调度的完成时间,找出所有机器中的最大值
    // len每台机器所需时间序列
    private int compute(){
        int tmp = 0;
        for(int i = 0; i < numberOfMachine; i++){
            if(len[i] > tmp){
                tmp = len[i];
            }
        }
        return tmp;
    }
 
    public static void main(String[] args){
        int numberOfTask = 7; // 任务数量
        int numberOfMachine = 3; // 机器数量
        int[] timeOfTask = new int[]{2, 14, 4, 16, 6, 5, 3}; // 每个任务的时间
        MachineWork machine =new MachineWork(numberOfTask, numberOfMachine, timeOfTask);
        machine.findTheSolution();
    }
}
 

解空间树的绘制:


由于时间不是很充沛,后续单调的操作我就不画了,自行补充...
————————————————
版权声明:本文为CSDN博主「何大康说」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/HeyWeCome/article/details/93887288

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值