分枝限界法解带期限作业调度问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013859301/article/details/78823287

这是在国科大的算法课上学到的算法,同时也是这门课上的一个作业。在做这个作业时遇到了很多的坑,网上查到的资料也很少,而且很多都是错的,因此在博客上记录下解决问题的过程。

问题描述

这里写图片描述

算法概述

这里写图片描述
这个伪代码中的几个函数解释如下:
- cost(T): 表达该答案节点造成的损失值
- u(T) : u(T)=iapi ,即该节点及其所有子树损失值的上界。
- cˆ(T) : cˆ(T)=i<m,iapi ,即该节点子树损失值的下界。

限界方法为:如果该子树损失下界大于整体最优值上界U,则剪去该分支。在查找过程中,每找到更低的 u(T) 值就用其来更新U。

坑1:但是如果看课件中的流程图,会发现其多次用了min(u(T) + eps,cost(T))这个函数。对于带期限作业调度问题,很容易想到u(T)和cost(T)两个函数应该是完全相同的,所以我大胆的把所有出现u(T)的地方都直接用cost(T)代替,最后也得到了正确的结果。不知道是不是我理解有误,如果有大神知道课件为何这么写,希望不吝赐教。
对上述算法有了基本了解后,结合下面这张状态空间树的图就很容易理解了:
这里写图片描述

但在具体实现时,还是会有一些细节需要注意。比如:
坑2:算法中还有一点没有提到的是如何判定一个节点是解节点。这个其实是在讲贪心算法时第一次提到带期限作业排序问题时提到的方法。即

检查J中作业的一个特殊序列就可判断J的可行性:按照作业期限的非降次序排列的作业序列。

也就是说,如果将已选作业的集合 Si 按作业期限的非降次序排列,检测这个调度能否满足条件即可。

实现

一开始我想用C++来实现,但是因为细节比较多(大量排序,归并和一些数组操作),所以索性改成python实现了。代码量并不大,只有100多行。思路基本上按照上面伪代码的思路。(其实将队列改为最小堆即可实现LC算法,但是python里没有现成的最小堆,因此只用列表模拟队列实现了FIFO算法)。源码如下:

import numpy as np

tasks = None
MAX = 10000
class Tree():
    def __init__(self):
        self.parent = None
        self.x = -1
        self.next_child_x_ = self.x
    def set_x(self, x):
        self.x = x
        self.next_child_x_ = x
    def get_next_children(self):
        t = Tree()
        self.next_child_x_ += 1
        if self.next_child_x_ >= tasks.shape[1]:
            return None
        t.set_x(self.next_child_x_)
        t.parent = self
        return t
    def get_task_set(self):
        x = []
        t = self
        while t.parent:
            x.append(t.x)
            t = t.parent
        return tasks[:, x]
    def get_c_hat(self):
        t = self
        c_hat = 0
        x = []
        while t.parent:
            x.append(t.x)
            t = t.parent
        for i in range(x[0]):
            if not i in x:
                c_hat += tasks[0, i]
        return c_hat
    def cost(self):
        if not self.is_valid():
            return MAX
        t = self
        cost = 0
        x = []
        while t.parent:
            x.append(t.x)
            t = t.parent
        for i in range(tasks.shape[1]):
            if not i in x:
                cost += tasks[0, i]
        return cost
    # 判断是否为解节点
    def is_valid(self):
        tasks = self.get_task_set()
        task_set = tasks[:, np.argsort(tasks[2, :])]
        end_time = 0
        for i in range(task_set.shape[1]):
            end_time += task_set[1][i]
            if end_time > task_set[2][i]:
                return False
        return True



def FIFOBB(T):
    E = T
    E.parent = None
    if E.is_valid():
        U = E.cost()
    else:
        U = MAX
        ans = 0
    queue = []
    while True:

        while True:
            X = E.get_next_children()
            if not X:
                break
            if X.get_c_hat() < U:
                queue.append(X)
                X.parent = E
                if X.is_valid() and X.cost() < U:
                    U = X.cost()
                    ans = X

                # 如果不是解节点就直接不更新U了
                # elif X.cost() < U:
                #     U = X.u + eps
        while True:
            if len(queue) == 0:
                print("least cost = ", U)
                while ans.parent:
                    print(ans.x + 1)
                    ans = ans.parent
                return
            E = queue[0]
            queue.pop(0)
            if E.get_c_hat() < U:
                break


def main():
    # p = [6,3,4,8,5]
    # t = [2,1,2,1,1]
    # d = [3,1,4,2,4]
    p = [5,10,6,3]
    d = [1,3,2,1]
    t = [1,2,1,1]
    global tasks
    tasks = np.array([p, t, d])
    T = Tree()
    FIFOBB(T)

if __name__ == '__main__':
    main()

运行结果:

least cost =  8
3
2

猜你喜欢

转载自blog.csdn.net/u013859301/article/details/78823287