这是在国科大的算法课上学到的算法,同时也是这门课上的一个作业。在做这个作业时遇到了很多的坑,网上查到的资料也很少,而且很多都是错的,因此在博客上记录下解决问题的过程。
问题描述
算法概述
这个伪代码中的几个函数解释如下:
- cost(T): 表达该答案节点造成的损失值
-
-
限界方法为:如果该子树损失下界大于整体最优值上界U,则剪去该分支。在查找过程中,每找到更低的
坑1:但是如果看课件中的流程图,会发现其多次用了min(u(T) + eps,cost(T))
这个函数。对于带期限作业调度问题,很容易想到u(T)和cost(T)两个函数应该是完全相同的,所以我大胆的把所有出现u(T)的地方都直接用cost(T)代替,最后也得到了正确的结果。不知道是不是我理解有误,如果有大神知道课件为何这么写,希望不吝赐教。
对上述算法有了基本了解后,结合下面这张状态空间树的图就很容易理解了:
但在具体实现时,还是会有一些细节需要注意。比如:
坑2:算法中还有一点没有提到的是如何判定一个节点是解节点。这个其实是在讲贪心算法时第一次提到带期限作业排序问题时提到的方法。即
检查J中作业的一个特殊序列就可判断J的可行性:按照作业期限的非降次序排列的作业序列。
也就是说,如果将已选作业的集合
实现
一开始我想用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