在有效时间内找到通过图的最短路径,附加约束条件是路径必须恰好包含n 个节点。
我们有一个有向加权图。它可能包含也可能不包含循环。我们可以使用 Dijkstra 算法轻松找到最短路径,但 Dijkstra 不保证边数。
我们能想到的最好办法是保留一个节点的最佳 n 路径列表,但这比 vanilla Dijkstra 使用大量内存。
它是一种简单的动态规划算法。
让我们假设我们想要从 vertexx
到 vertex y
。
做一个表格D[.,.]
,哪里是从起始顶点到顶点D[v,k]
的最短路径的成本。k
x
v
Initially D[x,1] = 0. Set D[v,1] = infinity for all v != x.
For k=2 to n:
D[v,k] = min_u D[u,k-1] + wt(u,v), where we assume that wt(u,v) is infinite for missing edges.
P[v,k] = the u that gave us the above minimum.
然后将最短路径的长度存储在 D[y,n] 中。
如果我们有一个边缘较少的图(稀疏图),我们可以通过只搜索连接的u
那个v
来有效地做到这一点。这可以通过一组邻接列表最佳地完成。
恢复最短路径:
Path = empty list
v = y
For k= n downto 1:
Path.append(v)
v = P[v,k]
Path.append(x)
Path.reverse()
最后一个节点是y
。之前的节点是P[y,n]
. 我们可以继续向后跟踪,我们最终会到达P[v,2] = x
一些v
。
我想到的替代方案是深度优先搜索(与 Dijkstra 的广度优先搜索相反),修改如下:
如果超出所需的顶点数,则停止“深度”
记录具有正确节点数的最短找到(迄今为止)路径。
运行时间可能很糟糕,但它应该在使用非常合理的内存量时得出正确的结果。
有趣的问题。您是否讨论过使用启发式图搜索(例如 A*),增加超过或低于节点数的惩罚?这可能会或可能不会被接受,但如果它确实有效,它可能比保留所有潜在路径的列表更有效。
事实上,您可以使用回溯来限制用于您讨论的 Dijkstra 变体的内存量。
一个算法的粗略想法:
令 A 为起始节点,令 S 为一组节点(加上一条路径)。不变的是,在第 n 步结束时,S 将所有距 A 正好 n 步的节点,并且路径将是该长度的最短路径。当 n 为 0 时,该集合为 {A (empty path)}。在第 n - 1 步给定这样一个集合,你可以从一个空集合 S1 开始到第 n 步,然后
for each (node X, path P) in S
for each edge E from X to Y in S,
If Y is not in S1, add (Y, P + Y) to S1
If (Y, P1) is in S1, set the path to the shorter of P1 and P + Y
只有 n 个步骤,并且每个步骤都应该小于 max(N, E),这使得整个算法对于密集图 O(n^3) 和对于稀疏图 O(n^2)。
这个算法取自 Dijkstra 的,尽管它是一个不同的算法。
假设我们想要 k 步简单 dp 解决方案中从节点 x 到 y 的最短距离
A[k][x][y] = min over { A[1][i][k] + A[t-1][k][y] } k 从 0 变化到 n-1
A[1][i][j] = r[i][j]; p[1][i][j]=j;
for(t=2; t<=n; t++)
for(i=0; i<n; i++) for(j=0; j<n; j++)
{
A[t][i][j]=BG; p[t][i][j]=-1;
for(k=0; k<n; k++) if(A[1][i][k]<BG && A[t-1][k][j]<BG)
if(A[1][i][k]+A[t-1][k][j] < A[t][i][j])
{
A[t][i][j] = A[1][i][k]+A[t-1][k][j];
p[t][i][j] = k;
}
}
trace back the path
void output(int a, int b, int t)
{
while(t)
{
cout<<a<<" ";
a = p[t][a][b];
t--;
}
cout<<b<<endl;
}