算法设计二(3)——汽车加油行驶问题

本文详细分析了汽车加油行驶问题的动态规划解法,指出了原始算法的漏洞,并提出使用单源最短路径优化,探讨了两种方法的时间复杂度。核心在于理解最优子结构和状态转移方程的改进,适用于复杂路径问题求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接:https://pan.baidu.com/s/1bspfq4eEOGp2tLM0kmbVMw?pwd=5y6f 
提取码:5y6f

算法分析与设计

时间

2020.4.23

 实验名称

汽车加油行驶问题

实验目的

通过上机实验,要求掌握动态规划算法的问题描述、算法设计思想、程序设计。

实验原理

设计一个算法,利用动态规划法的思想,根据题目所给出的条件,求出汽车从起点出发到达终点所付的最小费用。

实验步骤

问题分析:

  该问题有最优子结构,在(x,y)坐标处,汽车运动到(x,y)坐标的途径有(x-1,y)、(x,y-1)、(x+1,y)、(x,y+1),因此只需要取经这四个坐标到达(x,y)坐标所需花费的最小值即可得到到(x,y)处的最小值,如果(n-1,n)、(n,n-1)这两个坐标的最小花费确定,则可求得问题的解(x,y)处的最小花费;

  这里用f(x,y,0)、f(x,y,1)表示从(1,1)到(x,y)的最小费用和剩余能够行驶的距离;

  建立状态转移方程为:

  f(1,1,0)=0,f(1,1,1)=K;

  f(x,y,0)+=A、f(x,y,1)=K;(x,y)为油库;

  f(x,y,0)+=C+A、f(x,y,1)=K;(x,y)处非油库且油用尽;

  f(x,y,0)=min{f(x+s[i][0],y+s[i][1]),0)}+s[i][2]} 0<=i<4;

  其中数组s中保存的是四个方向移动;

解题步骤

  ①读取文件中题目信息;

  ②初始化**f数组,令f[][][0]全为整数最大值(f[1][1][0]起始地点为0),f[][][1]=K;

  ③循环进行④处理,若处理过程中标记位置1则处理结束后进行继续循环处理,直到某次标记位不为1,结束循环;(标记位置1是在f[][][0]有更小值而更新的时候,如果有最小值更新那么从更新的点的周围四个点就有可能会被更新,进而扩展到会影响f[n][n][0]的值);

  ④从第一行第一列开始遍历,从左至右从上至下遍历整个地图,对每一个点(i,j)求从它周围四个点到它所需费用的最小值(需要进行边界检查),如果最小值小于f[i][j][0]则说明可以更新这个点,因此将标记位置为1;

 ⑤从③的循环中出来后说明遍历图的最后一次没有一个点被更新,所有点都是最优解,因此f[n][n][0]为最优解,可以直接输出;

纠错

事实上跟同学讨论的时候发现这个解题方法具有非常大的漏洞,极大概率得出的结果会是错误的;

如图:

对于这样一张6*6的地图;

我们可以根据上述算法得到它最后所有子问题的最终解:

 ->

我们可以明显地看到,

经过(5,2)到达(5,5)的大小为13,也就是说(5,5)处的最优解应该是比108更小的13,进而整个问题的最优解也应该是(1,2)->(3,3)->

(5,2)->(5,5)->(6,6)的得到的最优解13;

然而用上面的解题步骤得到的解为106,明显错误;

原因其实很简单,(5,5)的解只跟(5,4)、(5,6)、(4,5)、(6,5)有关,这里(5,5)的解是根据(5,4)得来的;

但是呢,(5,4)的最优解来自于(3,3)经过(5,3)的路径,因为(5,3)的解按照上面的方法来的话如果经过(5,4)就会是比4大的11,不可能更新,因此此时(5,4)的解就与(5,2)没有关系了,这个时候(5,4)还剩下1步可走,到(5,5)剩余步数为0,需要建立加油站并加油;

因此,这种方法对于往回走会产生的最优解无法有效的反应,需要改进算法;

优化方法

一种最简单的优化方法就是令更新后的子问题的解(x,y)在当前行或列往后处理剩余步数的路径,比如剩余2步,那就处理(x+1,y)、(x+2,y)、(x,y+1)、(x,y+2),看(x,y)的最优值更新是否会对后面这些解有影响;

但是这种方法的时间复杂度会非常大;

因此用建图求单源最短路径的方法来重新做这个实验:

大致思路:

我们把每个点都看成一个状态就可以,只不过这里每个点会因为剩余油量的问题多出k个状态。每个点的状态相当于是(i,j,k)(i,j,k)了,分别表示在网格(i,j)这个点,剩余流量是k的一个状态。然后建图:

①对于(i,j)点是油库。那么我们对于(i,j,l)(i,j,l)到(i,j,k)(i,j,k)连一条边,花费为a。l的范围是0到k-1.

②对于(i,j)点不是油库。那么我们对于(i,j,l)(i,j,l)到(i,j,k)(i,j,k)连一条边,花费为a+c。l的范围是0到k-1.

③对于(i,j)点是油库。那么我们对于(i,j,k)(i,j,k)到(i1,j1,k−1)(i1,j1,k−1)连一条边,花费为0|b。这里表示的是这个点能够到达的相邻的点。花费按照横纵坐标的变化而不同。

④对于(i,j)点不是油库。那么我们对于(i,j,l)(i,j,l)到(i1,j1,l−1)(i1,j1,l−1)连一条边,花费为0|b。l的范围是1到k.这里表示的是这个点能够到达的相邻的点。花费按照横纵坐标的变化而不同。

然后跑一个(0,0,k)(0,0,k) 的最短路,更新到(n−1,n−1,l)(n−1,n−1,l) 的答案即可。l从0到k。

建图描述:

三维点表示的是(x,y,k),x,y代表坐标,k代表层数

1、 源点->(1,1,k),流量为1,花费为0

2、(x,y,k)->(x,y,k-1),流量为无穷大,花费符合条件2

3、当前点是否为油库:

  是油库:(x,y,t)t∈[0,k-1]->(x,y,k),流量为无穷大,花费为a

  不是油库:(x,y,0)->(x,y,k),流量为无穷大,花费为a+c

(n,n,t)t∈[0,k]->汇点,流量为无穷大,花费为0

算法步骤:

1、设立一个先进先出的队列用来保存待优化的结点。

 2、优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。

 3、这样不断从队列中取出结点来进行松弛操作,直至队列空为止

关键代码

关键代码(带注释)

首先是第一种解题步骤(有缺陷)的代码:

  1. dp部分代码

以上为初始化**f数组部分;

pd就是所定义的标志位;

(Prex,Prey)是可以到达(x,y)的前一个点(最多有四种);

比较经过(Prex,Prey)到达(x,y)所需的费用,取最小值

最小值与此时的f[x][y][0]比较,如果更小则更新,并将标记位置1;

对于向回走只能反应一步,但是可能多向后走几步就会出有一个更优的解;

因此这种方法并不适合所有情况;

接着是用新的方法实现的动规部分:

大致上为单源最短路径算法SPFA算法的扩展应用;

当前结点now如果遇到了加油站,则加油之后再入队列,下一次出队列后再处理;

当前now结点用完了油,并且没有加油站,则设油库加油并入队列;

否则,就根据当前结点now预期它的下一个结点,如果有更新则更新后令下一结点入队(入队列);

读取文件

测试结果

运行结果截图及分析

题目样例:

 

对于题目样例,往回走并不会影响到后面的子问题,因此运行无误;

而对于第二种方法:

最后一行为输出

对与往回走影响后面子问题解的情况,刚才的6*6网格可以输出正确的最优解;

时间复杂度分析:

第一种方法的时间复杂度为O(n^3);

第二种方法最坏时间复杂度为O(VE),顶点和边数的乘积;

实验心得

   这个实验相比较0-1背包问题还要更加的复杂,但是只要把握好了状态转移方程,紧紧围绕着方程式来进行程序的编写就可以很轻松的写出动态规划的代码了,但其中需要注意对子问题的解的更新可能会反过来对已经处理过的子问题的解产生影响,因此求过一次所有子问题的解后一定要确保所有子问题的解都已经是最优的了;第一种方法只能判断一步这种影响,因此它并不总是对的,而第二种方法的影响是持续的,因此可以证明是正确的;

1.问题描述 给定一个N*N 的方形网格,设其左上角为起点,坐标为(1,1),X 轴向右为正,Y 轴 向下为正,每个方格边长为1。一辆汽车从起点出发驶向右下角终点,其坐标为(N,N)。 在若干个网格交叉点处,设置了油库,可供汽车行驶途中加油汽车行驶过程中应遵守 如下规则: (1)汽车只能沿网格边行驶,装满油后能行驶K 条网格边。出发时汽车已装满油,在 起点与终点处不设油库。 (2)当汽车行驶经过一条网格边时,若其X 坐标或Y 坐标减小,则应付费用B,否则 免付费用。 (3汽车行驶过程中遇油库则应加满油并付加油费用A。 (4)在需要时可在网格点处增设油库,并付增设油库费用C(不含加油费用A)。 (5)(1)(4)中的各数N、K、A、B、C均为正整数。 算法设计: 求汽车从起点出发到达终点的一条所付费用最少的行驶路线。 数据输入: 输入数据。第一行是N,K,A,B,C的值,2 <= N <= 100, 2 <= K <= 10。第行起是一个N*N 的0-1方阵,每行N 个值,至N+1行结束。方阵的第i 行第j 列处的值为1 表示在网格交叉点(i,j)处设置了一个油库,为0 时表示未设油库。 各行相邻的2 个数以空格分隔。 结果输出: 将找到的最优行驶路线所需的费用,即最小费用输出. Sample input 9 3 2 3 6 0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 1 0 0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 Sample output 12
### 贪心算法解决汽车加油问题 当面对汽车加油问题时,贪心算法提供了一种有效的方法来最小化加油站停靠次数。假设有一辆汽车行驶一段距离并有多个加油站可供选择,在每站加满油箱的情况下,目标是最少停车加油次数。 为了实现这一目的,可以采用如下策略:每次尽可能远地到达下一个加油站而不至于耗尽燃料。具体来说: - 给定一系列位置上的加油站以及车辆的最大行程范围。 - 记录当前最远可抵达的位置,并遍历所有可能的加油站点。 - 如果发现无法继续前进到下一站,则在最近的一个可行站点加油[^1]。 下面是一个简单的Python函数用于模拟上述过程: ```python def min_refills(distance, tank_capacity, stops): num_refills = 0 current_position = 0 while current_position < (len(stops) - 1): last_stop = current_position while (current_position < (len(stops) - 1)) and \ (stops[current_position + 1] - stops[last_stop]) <= tank_capacity: current_position += 1 if current_position == last_stop: return "It&#39;s not possible to reach the destination." if current_position < (len(stops) - 1): num_refills += 1 extra_distance = distance - stops[-1] if extra_distance > tank_capacity: return "Cannot reach final destination with given fuel capacity." return num_refills ``` 此代码片段定义了一个`min_refills()` 函数,它接收三个参数——总路程长度 `distance` ,油箱容量 `tank_capacity` 和 加油站列表 `stops`. 它返回完成旅程所需的最少加油次数;如果不可能完成旅行则给出相应提示[^2].
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值