寻找最优解
这里提供了一种递归方法来解决您的问题。
让我们从一些定义开始:
- 令 A = (Ai)1 ≤ i ≤ N 为区域。
- 令 wi,j = wj,i 为从 Ai 到 Aj 和从 Aj 到 Ai 的时间成本。
- 令 ri 为访问区域 Ai 的奖励。
以下是将输出所需精确解决方案的递归过程:(伪代码)
List<Area> GetBestPath(int time_limit, Area S, int *rwd) {
int best_reward(0), possible_reward(0), best_fit(0);
List<Area> possible_path[N] = {[]};
if (time_limit < 0) {
return [];
}
if (!S.visited) {
*rwd += S.reward;
S.visit();
}
for (int i = 0; i < N; ++i) {
if (S.index != i) {
possible_path[i] = GetBestPath(time_limit - W[S.index][i], A[i], &possible_reward);
if (possible_reward > best_reward) {
best_reward = possible_reward;
best_fit = i;
}
}
}
*rwd+= best_reward;
possible_path[best_fit].push_front(S);
return possible_path[best_fit];
}
为了明确起见,我假设Ai和wi,j是全局可访问的。
解释
你从S开始。第一件事是什么?收集奖励并标记节点为已访问。然后你必须检查在S的N-1个邻居(我们称它们为NS,i,其中1 ≤ i ≤ N-1)之间选择哪个方向最好。
这与使用时间限制解决NS,i问题完全相同:
time_limit - W(S ↔ NS,i)
由于你标记了已访问的节点,所以当到达一个区域时,你首先检查它是否被标记。如果是,你就没有奖励...否则,你会收集并标记它为已访问...
依此类推!
结束条件是当时间限制(C)变为负数时。这告诉我们,我们已经达到了极限,不能继续进行更多的移动:递归结束。如果在达到时间限制C之前已经收集了所有奖励,则最终路径可能包含无用的旅程。你需要“修剪”输出列表。
复杂度?
哦,这个解决方案在复杂度方面太糟糕了!每次调用都会导致N-1次调用...直到达到时间限制。通过在最短的边上来回移动,可以得到最长的可能调用序列。让wmin成为这条边的权重。
显然,总体复杂度受NC/wmin.C/wmin的限制。
这是非常巨大的。
另一种方法
维护一个所有已访问节点的哈希表。
另一方面,维护一个未收集节点的最大优先队列(例如使用MaxHeap)。(堆的顶部是奖励最高的节点)。队列中每个节点Ai的优先级值设置为对偶(ri,E[wi,j])
- 弹出堆:
Target <- heap.pop()
。
- 使用Dijkstra算法计算到该节点的最短路径。
- 检查路径:如果路径成本太高,则该节点不可达,将其添加到不可达节点列表中。
- 否则,收集其中找到的所有未收集节点并...
- 从堆中删除每个收集的节点。
- 将目标设为新的起点。
- 在任何情况下,继续执行步骤1,直到堆为空。
注意:哈希表是跟踪收集节点的最佳选择。这样,我们可以在使用Dijkstra计算路径时以O(1)的时间检查节点。
同样,维护一个哈希表,指向堆中每个节点的位置,可能有助于优化沿路径收集节点时对堆进行“修剪”的操作。
一些分析
从复杂度的角度来看,这种方法略优于第一种方法,但可能不会导致最优结果。实际上,在某些图形配置上,它甚至可能表现得相当差。例如,如果所有节点都具有奖励r,除了一个节点T具有r+1和W(N ↔ T) = C(其中N是每个节点,C是常数),但其他边缘都是可达的,则这只会使您收集T并错过每个其他节点。在这种特殊情况下,最好的解决方案是忽略T并收集其他所有人,从而获得(N-1).r的奖励,而不仅仅是r+1。