1. 程式人生 > >分枝限界法解帶期限作業排程問題

分枝限界法解帶期限作業排程問題

這是在國科大的演算法課上學到的演算法,同時也是這門課上的一個作業。在做這個作業時遇到了很多的坑,網上查到的資料也很少,而且很多都是錯的,因此在部落格上記錄下解決問題的過程。

問題描述

這裡寫圖片描述

演算法概述

這裡寫圖片描述
這個虛擬碼中的幾個函式解釋如下:
- 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