这是天涯上很久以前的一个帖子,多年前求解过,但是具体过程写得不好,自己都看得都晦涩了。所以重新写一遍。
原题目:假设有一辆车,它的油箱恰好和一个油桶一样大,而且车上恰好可以运载一个油桶。假设一桶油可以让车开一百公里。现在在起点,车装满了油,另外起点还有100桶油。问,这车通过运油并途中自加油的方式,最远能离开起点多远?
首先简化一下题目,一桶油可以走一个单位长度(不用100公里,这样后面容易折算一些)
从某一个途中点A(i)运油到下一个途中点A(i+1),可作以下考虑:
1、把车辆的油箱里的油也计入,出发点A(i)有x桶油(x可以为小数),下一个加油点A(i+1)有y桶油(y也可以是小数)
2、为保证最大化运油效率,卡车从A(i+1)返回A(i)时,油箱留存的一点点油应该恰好用完,设距离为s,因此单程的油量也是s
3、总的往返次数为2m+1,其中返回次数为m,由1、2可得,x-y=(2m+1)*s。这里2m是往返m次,1是最后一次前往第i+1点。
4、前m次去,肯定要把油装满,即2桶油,去的路上用掉了s桶油,然后再装s桶油返回第i点,所以可以推论出:前m次实际运到的油量为(2-2*s)*m。(此处隐含了一个条件 s < 1)
5、最后一次前往第i+1点时,第i点剩下的油为 x-2m,然后把这剩下的油拖到第i+1点后,最后一次能运到第i+1点的油量为 x-2m-s。
6、考虑一种意外的情况,即到最后一次,由于第i点剩余的油量不足s了,就不值得回去了。这样是不是会因此而浪费一部分油呢?其实不会,如果出现这种情况,就应该缩小s,以满足最后一次的效率。当然最好的方案是,最后一次也能拉满,即也拉上 2桶油,即 x-2m = 2。
7、思考到这里,发现每次循环中 s 的设定,只有满足 x-2m = 2是效率最高的,即每个途中点的油量必须是 2的整数倍数。
8、由于起点(定义为第1点)的油量为101,不是2的整数倍数。所以在第2个点就必须设法将其凑整为2的整数倍数,最近的一个数就是100。这样,第一个关键问题,就是要用1的油耗量,让s尽可能的远。根据分析3,1=(2m+1)*s,其中m=50(因为先得把100桶油运走)。故 s=1/101。
9、 继续考虑到第3点,运到第3点98个油量,总油耗为2=(2m+1)*s,其中m=48,解得s=2/97
10、继续考虑到第4点,运到第4点96个油量,总油耗为2=(2m+1)*s,其中m=47,解得s=2/95
11、继续考虑到第5点,运到第4点94个油量,总油耗为2=(2m+1)*s,其中m=46,解得s=2/93
12、以此类推,,其中最后一个 2,是最后一次用2桶油不跑往返,直接跑直线的距离。上式使用电子表格可计算得5.86524。
以上的分析8,不一定是最优方案。以前用模拟退火,算到过5.87,但是具体算法还要再检查一下,再继续发布。
13、现在回到分析6,重新考虑 x 与 m之间的关系(暂不考虑 s)。根据分析4,当 x 不是 2的整数倍数时,必有 2m + z = x,其中 z < 2。因此 ,
是取整函数。而x是是 2的整数倍数时,根据分析6,有 2m+2 = x,即
。
现在进入模拟退火的算法思考:
1、如分析8-12,设定了包含49个运油点,下面不拘泥于运油点只有49个。假设有P个运油点,每个运油点油量为,从上一个点到当前运油点的抵达路程为
。所有P个运油点的
和
构成了一条路径(route),现在就要求一个路径(route),使
能有最大值。
2、先“随意”预定义一条路径,然后对路径进行随机微调,依据模拟退火的判决原则,决定微调结果是否能接受,反复进行这样的随机微调,直至模拟退火到达目标
3、随机微调的方式,包括下面几种调整方式
3.1 随机选择一个运油点,微调(增或减)油量,相当于移动其距离位置
3.2 在路径中随机增加或减少一个运油点
3.3 为保证增、减的微动变量相对稳定,应设定一个最小微动变量,即最小油量变化量。上述两种变化,都必须保证在相邻的两个路径点之间满足最小油量变化量。当然这个最小油量变化量也就是本算法能达到的最小精度。毕竟不是求解析解,只是在求数值解。
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function
import random
import math
import copy
# 最小油量变化量
global Oil_Step
Oil_minStep = 0.00003 # 这个参数决定了每次的微变量大小
#Boltzmann_constant=1.3806505*10^(-23) #kB
def One_CircleCount (x):
# 某一段的往返次数,x 为起始油量 (见 分析13)
m = int(x/2)
if m *1.0 == x/2 :
# 如果 x 是2的整数倍
m = x/2 - 1
return m
def One_Distance (x, y):
# 某一段的运送距离,x 为起始油量, y 为运到油量 (见 分析3 )
m = One_CircleCount (x)
L = (x - y) / (2*m + 1)
return L
def Route_Distance (Route):
Distance = 0
for i in range(1,len(Route)):
Distance += One_Distance(Route[i-1],Route[i])
return Distance
def Make_Next_Route (Route):
Not_New_Route_Generate = True
while Not_New_Route_Generate:
mRand = random.randint (1,4)
if mRand == 1 :
# 1 右移动某个路径点(左边方向是起点,右边是终点)
m = random.randint(1,len(Route)-2)
#移动谁,不能是第一点101,也不能是最后一点2
if Route[m]-Oil_minStep > Route[m+1]:
if One_Distance (Route[m-1],Route[m]-Oil_minStep) < 1.0:
Route[m] = Route[m]-Oil_minStep
Not_New_Route_Generate = False
elif mRand == 2 :
# 2 左移动某个路径点
m = random.randint(1,len(Route)-2)
if Route[m]+Oil_minStep < Route[m-1]:
if One_Distance (Route[m]+Oil_minStep, Route[m+1]) < 1.0:
Route[m] = Route[m]+Oil_minStep
Not_New_Route_Generate = False
elif mRand == 3 :
# 3 增加一个路径点
m = random.randint(0,len(Route)-2) # 在第m点后增点
if Route[m+1]-Route[m] > Oil_minStep:
Route.insert(m+1, Route[m]+(Route[m+1]-Route[m])/2)
Not_New_Route_Generate = False
else:
# 4 减去一个路径点
m = random.randint(1,len(Route)-2)
if One_Distance (Route[m-1], Route[m+1]) < 1.0:
del Route[m]
Not_New_Route_Generate = False
def MainRun ():
random.seed()
Route = []
Step_Value = 101
# 以下生成一条路径list,路径上每个点的油量差为 1
# list中存储的是运到的油量
# 第0点油量 101, 最后1点油量 2
while Step_Value > 2:
Route.append(Step_Value)
Step_Value = Step_Value - 0.001 # 这个参数是一个奇葩,越小,初始的总距离越远
Route.append(2)
R_Distance = Route_Distance(Route)
"""
for i in range(0,len(Route)):
print (Route[i],)
print ("")
"""
T = 200
# 估计最远距离为 5-6 之间,而初始距离 3.8,那么每次的微变量 dE 瞎猜最大也就是0.2
# 根据 exp(-dE/T) 的判决式
# 把模拟退火起点温度 2,温度每次下降 0.001,温度下降到 0.001,完成退火
cc = 0
while T > 1:
print ("\n%d \t %f \t %e" %(cc, T, 2+R_Distance) ,end="",flush=True)
cc += 1
T -= 0.001
j = 20
while j > 0:
# 内循环用来模拟同一温度下多次的状态转移,也称为抽样稳定准则
# (为什么称为“抽样稳定”准则,我的理解是等温过程在邻域选择下
# 一次状态转移,一般需要在邻域内采样多次,进行多次状态转移,
# 进而达到这一温度下的稳定状态)
tmp_R = copy.copy(Route)
Make_Next_Route (tmp_R)
tmp_R_Distance = Route_Distance(tmp_R)
delta_Distance = R_Distance - tmp_R_Distance
if delta_Distance < 0:
# 新目标值更优
j -= 1
Route = tmp_R
R_Distance = tmp_R_Distance
print ("|", end="", flush=True)
elif delta_Distance == 0:
# 新目标没有变化,本次循环无效
pass
else:
# 新目标值较劣,delta_Distance >0
# 使用 metropolis principle
j -= 1
k = 10000000000 # 奇葩的优化参数,根据数据变化趋势进行手动调整
BozF = - delta_Distance*k / T
#try:
Boz = math.exp (BozF)
BozRand = random.random()
print ("*", end=" ", flush=True)
if (BozRand < Boz) :
Route = tmp_R
R_Distance = tmp_R_Distance
print ("_", end=" ", flush=True)
print ("%d \t %f \t %e" %(cc, T, R_Distance))
for i in range(1,len(Route)):
print ("第 %d 段: " %(i), end="")
print ("距离: %e," % (One_Distance(Route[i-1],Route[i])), end="")
print ("往返次数: %d," % (2*One_CircleCount(Route[i-1],Route[i])+1),end="")
print ("搬运油量: %e" % (Route[i]))
if __name__ == '__main__':
MainRun()
##1使用随机数发生器产生一个随机的分子构型。
##2对此分子构型的其中粒子坐标做无规则的改变,产生一个新的分子构型。
##3计算新的分子构型的能量。
##4比较新的分子构型于改变前的分子构型的能量变化,判断是否接受该构型。
##若新的分子构型能量低于原分子构型的能量,则接受新的构型,使用这个构型重复再做下一次迭代。
##若新的分子构型能量高于原分子构型的能量,则计算玻尔兹曼因子,并产生一个随机数。
## 若这个随机数大于所计算出的玻尔兹曼因子,
## 则放弃这个构型,重新计算。 x1 := exp(-delta_distance*k/T);
## k = 1.3806505(24) × 10^23 J/K
## 若这个随机数小于所计算出的玻尔兹曼因子,则接受这个构型,使用这个构型重复再做下一次迭代。
##5如此进行迭代计算,直至最后搜索出低于所给能量条件的分子构型结束。
#@ignore
代码中的几个参数需要手动调整,详见注释。简单优化一下参数,可以得到5.885451的结果。
但是似乎每次运油点的间隔越小,总距离就越大。
其中的解析解的数学推导可能会涉及到变分和泛函,一直也没有精力去进一步学习研究了。