CRP
Carnival Route Programming - 游乐园路径规划
问题描述
游乐园每天会开放多个游玩项目,每个项目的开放时间段和游玩所需时间(包括排队等待的时间)都不同。设游客游玩个项目,每个项目的游玩所需时间为,开放开始时间为,开放结束时间为,从项目移动到项目的路程所需时间为,其中为游玩项目,为游乐园大门。要求规划一个项目游玩顺序,使得游客在规定开放时间段游玩所有项目,并且总游玩时间最短。即求下列问题:
其中为一个从游乐园大门开始的项目序列 ,为游客真实到达项目的时间。式表示在项目序列下路程所需的总时间,式表示在项目序列下的总等待时间 ,式为路程和等待的总时间。由于游玩所需的时间固定不变,所以在这个问题中只需要考虑最小化式即可,目标函数如下:
注1:这里的等待时间并非排队等待时间,而是指游客到达了项目位置,而项目还没到开放开始时间,从而产生的等待时间。
问题分解
该问题可以看作一个基础问题附加上一定约束条件。其中基础问题为在完全图中给定一个起点,求一条最短通路,约束条件即为访问点的时间必须在规定范围之内。首先对于基础问题的求解十分简单,可以使用贪心算法每次向离当前点最近的点前进(先不急反驳)。该过程可以使用伪码描述如算法1所示。
算法1:使用贪心算法求解基础问题
输入:T (每条道路所需的时间)
N (游玩项目总数)
输出:X (最优游玩序列)
1: U ← {1,...,n}
2: X ← ∅
3: i ← 0
4: for 1 to N do
5: for j in U do
6: m ← min(T[i][j],T[i][m])
7: end for
8: X ← X ∪ m
9: i ← m
10: end for
11: return X
基础问题得到了最快的路径,它并不一定能满足时间段要求。但给出了一个启示,即每次寻找的点应为满足时间段要求的最近点。由于满足时间段要求是根据每次选择动态变化的,没有办法一开始就知道选择这个点是否可行,此时我们可以用到回溯算法。核心思想为每次选择的时候将临近点按照式规定的总时间逆序排序,首先选择最近的点开始下一次选择,直到发生时间冲突,回溯到上一次选择中,选择次近的点继续选择,一直循环直到找到一条满足时间段要求的通路,该过程可使用如算法2描述的伪代码实现。
算法2:使用回溯算法求解带时间段条件的路径规划问题
输入:T (每条道路所需的时间)
N (游玩项目总数)
输出:X (最优游玩序列)
1: U ← {1,...,n}
2: i ← 0
3: A[i] ← 0
4: X ← reverse(recall(i,U))
5: return X
算法3:recall 使用回溯算法求解带时间段条件的路径规划问题中的递归函数
输入:i (当前访问的项目下标)
U (未访问序列)
输出:c (是否发生冲突)
X (当前递归排序完毕的项目序列)
1: c ← False
2: X ← ∅
3: if is_conflict then
4: return True,∅
5: U ← time_sort(U)
6: for j in U do
7: A[j] ← compute_arrive_time(i,j)
8: U ← U - j
9: c,X ← recall(j,U)
10: if not c then
11: break
12: end if
13: U ← U + j
14: end for
15: if c then
16: return True,∅
17: end if
18: X ← X ∪ i
19: return Flase,X
经计算,时间复杂度最好为,最差为,平均为。
优化
回溯算法解决该问题有两个很大的缺点,其一是每次都要从整个未访问的点中求解,其中很多点是明显不能优先访问的,其二是冲突的反应太慢,直到剩下的所有点都不能走的时候才发现冲突,此时可能需要回溯很多次才能找到错误的根源。因此,提出一个按优先级排序的快冲突响应算法(PSFCR)。
PSFCR算法核心思路是在每次递归计算前建立节点之间的支配关系,将非支配的点参与计算。此处的支配关系支配表示,如果在之前访问,就无法满足时间段要求。而且在计算支配关系的时候,若发现和互相支配,则证明当前选择的节点无论怎么继续走都会发生冲突,即找到了一种可以只需要试探一次就能发现冲突的快冲突响应方法,PSFCR算法可以使用伪码描述为算法4。
算法4:PSFCR 按优先级排序的快冲突响应算法
输入:T (每条道路所需的时间)
N (游玩项目总数)
输出:X (最优游玩序列)
1: U ← {1,...,n}
2: i ← 0
3: A[i] ← 0
4: c,F ← non_domination_sort(i,U)
5: if c then
6: return ∅
4: X ← reverse(PSFCR_recall(i,U,F))
5: return X
算法5:non_domination_sort 非支配排序算法
输入:i (当前访问的项目下标)
U (未访问序列)
输出:c (是否发生冲突)
F (非支配的项目序列)
1: F ← ∅
2: d ← {0,...,0}
3: for i in range(0,len(U))
4: for j in range(i+1,len(U))
5: if i ≺ j then // means i dominate j
6: d[i]++
7: elseif j ≺ i then
8: d[j]++
9: end if
10: if i ≺ j and j ≺ i then
11: return True,∅
12: end if
13: end for
14: end for
15: for i in U
16: if d[i]=0 then
17: F ← F ∪ i
18: F ← time_sort(F)
19: return False,F
算法6:PSFCR_recall PSFCR中的递归函数
输入:i (当前访问的项目下标)
U (未访问序列)
F (非支配的项目序列)
输出:c (是否发生冲突)
X (当前递归排序完毕的项目序列)
1: c ← False
2: X ← ∅
3: if F=∅ and U≠∅ then
4: return True,∅
5: for j in U do
6: A[j] ← compute_arrive_time(i,j)
7: c,F ← non_domination_sort(i,U)
8: if c then
9: continue
10: end if
11: U ← U - j
12: c,X ← PSFCR_recall(j,U,F)
13: if not c then
14: break
15: end if
16: U ← U + j
17: end for
18: if c then
19: return True,∅
20: end if
21: X ← X ∪ i
22: return Flase,X
经计算,时间复杂度最好为,最差为,平均为。
进一步优化
这里要推翻一下最开始的假设了。细心的朋友可能会发现我之前根本是在胡说八道,没错,基础问题用贪心算法求出来的解并不是最优解,只是一个比较好的可行解而已。具体如下图所示。
这个问题主要出在区域选择上,贪心算法有可能会先走到一个游玩项目比较偏远的区域,导致回到其他区域时花费异常大的时间。如果还要按照之前的方法,就需要穷举出所有的可行解。当然因为需要所有可行解,那么每个节点的排序操作就可以省略了,经计算无PSFCR时间复杂度为,有PSFCR时间复杂度为,其中为结点个数,为可行解数目。当时,有PSFCR的方法更优。即,可行解越少(约束越强)PSFCR算法越好。
这样确实可以求出最优解,但是在可行解偏多的情况下速度很不理想。由于其本身是一个NP问题,故应该找到一个符合时间约束的较优解即可。使用贪心算法的PSFCR就给出了一个较优解,再次基础之上,添加区域选择算法(AS),使算法获得更为优秀的可行解。要进行区域选择,首先要进行区域划分。
区域划分
假设线段长度等于线段长度,小于60°时,,而大于60°时,。故使用60°作为一个分界值。假设当前所在点为点,任意点与,使得,即,则与属于不同的区域,反之属于相同区域。
区域选择
如果还是需要求出最优解,在划分区域后,可以将PSFCR的支配解按照区域分成一个二维数组。一个区域间算出可行解后,即可跳过整个区间,到下一个区间进行搜索。此时PSFCR-AS的时间复杂度为,其中为总区域数,并有。由此可见区域划分极大提升了算法效率。
但由于该算法用于一个即时交互系统,只需要求出一个较优解即可。为了使得时间复杂度变为,就需要人为决定区域之间的访问顺序。目前可以根据平均距离或最远距离来判断访问顺序。为了减少回头路的长度,应该优先选择距离较近的区域访问。但平均距离和最远距离都只能解决部分情况,依概率,平均距离可以解决的情况更多。故我们选择先访问平均距离最短的区域。该算法只能求出较优解,伪码如下。
[PSFCR-AS/AD伪码]