最优时间表问题

前言

2019年11月13日 更新更高效版本,运用动态规划解决重叠子问题的思想进行了优化。并对之前含糊不清的解释进行了重新描述,因为作业太过马虎,被要求重做。


最近有在上算法分析与设计的课,所以时隔多年又开始做作业了。
上课课本用的是王晓东的《计算机算法分析与设计》
以后会经常更新一些课本的课后习题
这次是第85页的3-16题
而且读研的学校开了一门python课,所以能用python实现的尽量python了,如果有时间可能补上Java版本的。

题目详情

一台精密仪器的工作时间为 n 个时间单位, 与仪器工作时间同步进行若干仪器维修程序. 一旦启动维修程序, 仪器必须进入维修程序。
#如果只有一个维修程序启动, 则必须进入该维修程序。如果在同一时刻有多个维修程序, 可任选进入其中的一个维修程序。
#维修程序必须从头开始,不能从中间插入. 一个维修程序从第 s 个时间单位开始, 持续 t 个时间单位, 则该维修程序在第 s+t-1 个时间单位结束。
为了提高仪器使用率, 希望安排尽可能少的维修时间。

输入要求:由文件input.txt给出输入数据,第一行两个数表示n和k。n表示机器的工作时间单位看,k是维修程序数。在接下来的k行,每行有2个表示维修程序的整数s和t,该维修程序从第s个时间单位开始,持续t个时间单位

输出要求:将计算出的最短维修时间输出到文件output.txt

输入文件示例

input.txt

15 6
1 2
1 6
4 11
8 5
8 1
11 5

输出文件示例

output.txt

11

分析

机器工作的时候如果遇到维修程序启动,则需要立即进入维修程序。
设best(i)是求从第i个时间单位开始到第n个时间单位结束的最短单位时间,可以看出问题求解是有最优子结构的。
也就是说,best(i)满足一定的递归关系
显然此问题就是求best(1)。而总问题best(1)可以分解为求子问题到第n个单位时间结束的最短维修时间,对各个维修路径的最小值进行求解min,然后返回其值即可
输入:机器工作时间单位n,k是维修程序数,以下k行输入的是各个维修程序的开始时间s和持续时间t
输出:最短维修时间
深入考虑:可以设2个列表s、t分别存放维修开始时间和持续时间
①当i时间点有检修程序时:best(i) = min{best(s[j]+t[j])} j是在数组s中s[j] = i的位置,j可以有多个值
②当i时间点无检修程序时:best(i) = best(i+1) - 1 ,也就是将时间点往后推
③当涉及到best(n+1)问题,将其值设为n。这也是递归的终止条件

这样是因为当无检修程序时,时间点会逐渐往后推,一直递增到n+1过界,
这时设置一个递归终止条件,让式子往前推到上一步,即best(i+1)-1,这样依次递减能够得出对应的best(i)

公式:在这里插入图片描述

旧的源码

# 题目:
# 一台精密仪器的工作时间为 n 个时间单位, 与仪器工作时间同步进行若干仪器维修程序. 一旦启动维修程序, 仪器必须进入维修程序。
# 如果只有一个维修程序启动, 则必须进入该维修程序。如果在同一时刻有多个维修程序, 可任选进入其中的一个维修程序。
# 维修程序必须从头开始,不能从中间插入. 一个维修程序从第 s 个时间单位开始, 持续 t 个时间单位, 则该维修程序在第 s+t-1 个时间单位结束。
# 为了提高仪器使用率, 希望安排尽可能少的维修时间。


# 分析:
# 机器工作的时候如果遇到维修程序启动,则需要立即进入维修程序。
# 当同时存在多个维修程序时,可对各个维修程序进行求问题的子结构问题。
# 也就是说,可以设置一个递归函数best(i),作用是求从第i个单位时间开始到第n个单位时间结束的最短维修时间。
# 那么显然此问题就是求best(1)。而总问题best(1)可以分解为求子问题到第n个单位时间结束的最短维修时间,对各个维修路径的最小值进行求解min,然后返回其值即可
# 输入:机器工作时间单位n,k是维修程序数,以下k行输入的是各个维修程序的开始时间s和持续时间t
# 输出:最短维修时间
with open('input16.txt', mode='r+') as f:
    list1 = list(f.readline().split())  #从文件中读取第一行数据,并将数据分别赋给n,k
    n = int(list1[0])
    k = int(list1[1])
    s = []  # 创建s数组和t数组分别用来存放第一列数据和第二列数据
    t = []
    for i in range(k):  #将第一二列数据分别存入s、t数组
        temp = list(f.readline().split())
        s.append(int(temp[0]))
        t.append(int(temp[1]))

    def best(i):
        # i时间点有检修程序时:best(i) = min{best(s[j]+t[j])}    j是在数组s中s[j] = i的位置,j可以有多个值
        # i时间点无检修程序时:best(i) = best(i+1) - 1 ,也就是将时间点往后推
        # 当涉及到best(n+1)问题,将其值设为n,这样是因为前面的步骤容易使i递增,需要控制。而且当i超过n,代表维修时间至少为n
        if i >= n + 1:  #当i的值过界,返回n
            return n
        if i not in s:  #当i不在s数组中,也就是在i时间点没有检修程序
            return best(i + 1) - 1
        else:   #当i在s数组中,也就是在i时间点存在检修程序时,对所有这个点能够进行检修的程序进行best()操作,并求出他们的min值。
            # count = s.count(i)
            min = 0 #min值初始化为0
            for j in range(k):  #遍历数组对每个s[j]进行逐个比较
                if(s[j] == i):
                    temp = best(s[j] + t[j])    #设立临时值,与min值进行比较
                    if (min == 0) or (min != 0 and temp < min):
                        # 当min值未进行更改或是已经更改且temp已经比min值小,对min值进行替换,使min始终保持最小
                        min = temp
                        
            return min

with open('output16.txt', mode='w+') as f:
    f.write(str(best(1)))

运行结果

符合示例

拓展及改进

想到的改进是因为这道题存在于第三章的动态规划课后习题里面
而动态规划就是有着重叠子问题,想到在算法中确实有着大量的重复计算,可以将这些重复计算进行数组的存储。
对代码的改进就是新增了一个storage数组来存储,当进行新的递归运算时先进性对此数组的查询,此思想又称:备忘录思想。想详细了解请百度。
在此只贴出python代码

改进后的源码

# 题目:
# 一台精密仪器的工作时间为 n 个时间单位, 与仪器工作时间同步进行若干仪器维修程序. 一旦启动维修程序, 仪器必须进入维修程序。
# 如果只有一个维修程序启动, 则必须进入该维修程序。如果在同一时刻有多个维修程序, 可任选进入其中的一个维修程序。
# 维修程序必须从头开始,不能从中间插入. 一个维修程序从第 s 个时间单位开始, 持续 t 个时间单位, 则该维修程序在第 s+t-1 个时间单位结束。
# 为了提高仪器使用率, 希望安排尽可能少的维修时间。


# 分析:
# 机器工作的时候如果遇到维修程序启动,则需要立即进入维修程序。
# 设best(i)是求从第i个时间单位开始到第n个时间单位结束的最短单位时间,可以看出问题求解是有最优子结构的。
# 也就是说,best(i)满足一定的递归关系
# 显然此问题就是求best(1)。而总问题best(1)可以分解为求子问题到第n个单位时间结束的最短维修时间,对各个维修路径的最小值进行求解min,然后返回其值即可
# 输入:机器工作时间单位n,k是维修程序数,以下k行输入的是各个维修程序的开始时间s和持续时间t
# 输出:最短维修时间
# 深入考虑:可以设2个列表s、t分别存放维修开始时间和持续时间
# ①当i时间点有检修程序时:best(i) = min{best(s[j]+t[j])}    j是在数组s中s[j] = i的位置,j可以有多个值
# ②当i时间点无检修程序时:best(i) = best(i+1) - 1 ,也就是将时间点往后推
# ③当涉及到best(n+1)问题,将其值设为n。这也是递归的终止条件
#   这样是因为当无检修程序时,时间点会逐渐往后推,一直递增到n+1过界,
#   这时设置一个递归终止条件,让式子往前推到上一步,即best(i+1)-1,这样依次递减能够得出对应的best(i)
with open('input16.txt', mode='r+') as f:
    list1 = list(f.readline().split())  #从文件中读取第一行数据,并将数据分别赋给n,k
    n = int(list1[0])
    k = int(list1[1])
    s = []  # 创建s数组和t数组分别用来存放第一列数据和第二列数据
    t = []
    storage = [0 for i in range(n+1)]   #备忘录数组,防止重叠子问题的重复计算,进一步提高算法时间效率
    for i in range(k):  #将第一二列数据分别存入s、t数组
        temp = list(f.readline().split())
        s.append(int(temp[0]))
        t.append(int(temp[1]))

    def best(i):
        # i时间点有检修程序时:best(i) = min{best(s[j]+t[j])}    j是在数组s中s[j] = i的位置,j可以有多个值
        # i时间点无检修程序时:best(i) = best(i+1) - 1 ,也就是将时间点往后推
        # 当涉及到best(n+1)问题,将其值设为n。
        if i >= n + 1:  #当i的值过界,返回n(这也是递归终止条件)
            return n
        if i not in s:  #当i不在s数组中,也就是在i时间点没有检修程序
            return best(i + 1) - 1
        else:   #当i在s数组中,也就是在i时间点存在检修程序时,对所有这个点能够进行检修的程序进行best()操作,并求出他们的min值。
            if storage[i]!=0:   #进行新的递归运算时先进行对此数组的查询,看此子问题是否已经经过计算
                return storage[i]
            else:
                min = 0 #min值初始化为0
                for j in range(k):  #遍历数组对每个s[j]进行逐个比较
                    if(s[j] == i):
                        temp = best(s[j] + t[j])    #设立临时值,与min值进行比较
                        if (min == 0) or (min != 0 and temp < min):
                            # 当min值未进行更改或是已经更改且temp已经比min值小,对min值进行替换,使min始终保持最小
                            min = temp
                            
                storage[i] = min
                return min

with open('output16.txt', mode='w+') as f:
    f.write(str(best(1)))

复杂度分析

由优化后的思路分析可知,此算法的时间复杂度和仪器的工作单位时间n和维修程序数k有关系
所以算法时间复杂度应该用O(n,k)来表示。
而O(n,k) = n*k
Ω(n,k) = n+k
空间复杂度为O(k)

运行结果展示

运行结果展示

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值