博客主体转载自:【算法学习】Dynamic Programming动态规划--例题的python实现_dynamic programming python-CSDN博客,选取了其中的“过河问题”部分,并在此基础上进行了少量修改。
问题描述:
在一个夜黑风高的晚上,有n(n <= 50)个小朋友在桥的这边,现在他们需要过桥,但是由于桥很窄,每次只允许不大于两人通过,他们只有一个手电筒,所以每次过桥的两个人需要把手电筒带回来,i号小朋友过桥的时间为T[i],两个人过桥的总时间为二者中时间长者。问所有小朋友过桥的总时间最短是多少。
解决思路:
我本以为这是个很简单的问题:
每次都是最快的那个人把手电筒拿回来不就最省时间吗?
N-1每个人过河1次,最快的那个人陪N-1个人过河N-1次,返回了N-2次。
花费时间是N-1个人的过河时间+ 最快的人的时间×(N-2).
然后原文也写了,这个基于贪心的第一直觉是有问题的!
其实:
有时候真的可以这样
有时候却是让最快的两个人送回手电最快。
比如有4个小朋友,过桥时间为a[1]、a[2]、a[3]、a[4](a[1]到a[4]依次增大)。
按照1,2送回手电的过桥步骤(以下简称方法1):
第一步:1和2过去,花费时间a[2],然后1回来(花费时间a[1]);
第二歩:3和4过去,花费时间a[4],然后2回来(花费时间a[2]);
第三步:1和2过去,花费时间a[2],总耗时a[1] + 3a[2] + a[4]。
按照贪心过桥步骤(以下简称方法2):
第一步:1和2过去,花费时间a[2],然后1回来(花费时间a[1]);
第三步:1和3过去,花费时间a[3],然后1回来(花费时间a[1]);
第一步:1和4过去,花费时间a[4],总耗时2a[1] + a[2] + a[3] + a[4]。
方法1的耗时减方法2的耗时: -a[1] + 2a[2] - a[3];
也就是说当 2a[2] 大于 a[1] + a[3], 就是方法2是最优解。
也就是说当 2a[2] 小于 a[1] + a[3], 就是方法1是最优解。
代码描述:
1. 每个人的耗时从小到大到排序:T[1…n]
2. 边界:
① i =0: 0
② i = 1: T[1]
③ i = 2: T[2]
3. 子问题; i 个人完成过河的最优时间opt[i]
4. 如果每个人都只和第一个人过河: opt[i] = opt[i-1]+T[1]+T[i]
5. 如果送手电的还包括第二个人:opt[i] = opt[i-2]+T[1]+T[i]+T[2]+T[2]
即所谓的河这边剩两人,其中一个肯定是T[2]
正是因为
T[2]送回手电+T[1]T[2]过河的时间< T[1]送手电+T[1]陪最后一个人过河的时间
才会有这种情况的存在
6. 最优解:4,5的较小值
代码:
def solution(time,N):
def cal_opt(time,N,opt):
opt[1] = time[0]
opt[2] = time[1]
# 子问题规模3~N
for i in range(3,N+1):
# 子问题求最优
sum_1 = opt[i-1]+time[0]+time[i-1]
sum_2 = opt[i-2]+time[0]+time[i-1]+time[1]+time[1]
opt[i] = min(sum_1,sum_2)
return opt
# 边界直接返回
if N ==0:
return 0
if N == 1:
return time[0]
if N == 2:
return time[1]
# 初始状态
opt = [0 for i in range(N+1)]
opt = cal_opt(time,N,opt)
return opt[N]
time = [1,2,5,10]
time = sorted(time)
print(solution(time,len(time)))
代码解释:
最后的 opt
,它是一个列表,用于存储每个子问题的最优解。在函数开始时初始化为全零列表,并在计算过程中逐步填充。返回时,取出 opt[N]
即可得到问题规模为 N 的最优解。
结果: