在图表中找到两点之间的最短路径是一个经典的算法问题,有很多好的答案(Dijkstra's algorithm,Bellman-Ford等等。)我的问题是,是否有一个有效的算法,给定定向加权图,一对节点s和t以及值k,找到s和t之间的第k个最短路径。如果有多条相同长度的路径都与第k个最短路径相关,那么算法可以返回任何路径。
我怀疑这个算法可能在多项式时间内完成,但我知道longest path problem可能会导致NP难以减少。
有没有人知道这样的算法,或者是否表明它是NP难的?
答案 0 :(得分:10)
答案 1 :(得分:9)
最佳(且基本上是最优的)算法归因于Eppstein。
答案 2 :(得分:2)
从可用的第K条最短路径算法中,Yen的算法最容易理解。主要是因为Yen的算法在计算第K个最短路径之前首先需要计算所有K-1个最短路径,因此可以将其分解为子问题。
此外,由于每个子问题都使用标准的最短路径算法(例如Dijkstra’s algorithm),因此从第一最短路径问题到 Kth是更自然的扩展最短路径问题。
这是在具有相等权重边缘的示例图中查找第三条最短路径的方法。
第一条最短路径(K = 1)
如果我们要寻找起点和终点之间的第一条最短路径(此处为D
和F
之间),则可以运行Dijkstra的算法。第一次迭代时,Yen算法的整个代码为:
shortest_1 = Dijkstra(graph, D, F)
给出一个起始图,它给出了第一条最短路径(K = 1)。
第二个最短路径(K = 2)
找到第二条最短路径的直觉是走第一条最短路径,但“ Dijkstra”算法沿另一条稍微不那么理想的路线“强制”。颜氏算法沿另一条路线“强迫”迪杰斯特拉算法的方法是通过删除属于第一条最短路径的一条边。
但是我们要去除哪一条边以获得下一条最短路径?我们需要尝试一个接一个地移除每个边缘,然后看看哪个边缘移除为我们提供了下一条最短路径。
首先,我们尝试删除边缘D-E
(shortest_1
中的第一个边缘),然后通过运行Dijkstra(graph_1, D, F)
完成最短路径。我们将节点D
到D
的最短路径(这只是节点D
本身)与从节点D
到{{1 }}。这为我们提供了另一条路径F
。
然后,我们尝试删除边缘D->F
(E-F
中的第二条边缘),然后通过运行shortest_1
完成最短路径。我们将节点Dijkstra(graph_2, E, F)
到D
的最短路径与节点E
到E
的新最短路径结合起来。这为我们提供了另一种替代路径F
。
第二次迭代的过程如下:
D->F
最后,选择新路径中的最短路径作为第二条最短路径。
第三条最短路径(K = 3)
就像通过从第一条最短路径中删除边来找到第二条最短路径一样,通过从第二条最短路径中删除边来找到第三条最短路径。
这次,新的细微差别是,当我们删除边缘// Try without edge 1
graph_1 = remove_edge(graph, D-E)
candidate_1 = shortest_1(D, D) + Dijkstra(graph_1, D, F)
// Try without edge 2
graph_2 = remove_edge(graph, E-F)
candidate_2 = shortest_1(D, E) + Dijkstra(graph_2, E, F)
shortest_2 = pick_shortest(candidate_1, candidate_2)
(D-A
中的第一个边缘)时,我们也希望删除边缘shortest_2
。如果不这样做,仅除去边缘D-E
,则在D-A
上运行Dijkstra时,我们将再次找到第一条最短路径(graph_3
),而不是第三条最短路径!
对于D-E-F
和graph_4
,我们不需要删除其他任何边缘,因为使用这些边缘不会给我们以前看到的最短路径。
因此,总体过程看起来类似于找到第二条最短路径,但有一点细微的差别,我们除了第二条最短路径外,还希望删除一些在第一条最短路径中看到的边。决定是基于graph_5
和shortest_1
是否共享一条通向被删除边缘的子路径。
shortest_2
请注意,这一次我们如何选择最短路径时,我们会考虑迭代2中未使用的候选对象(即// Try without edge 1
edges_3 = [D-A]
if shortest_1 and shortest_2 share subpath up to node D:
// True because both shortest_1 and shortest_2 have D in shortest path
// D-E is edge in shortest_1 that is connected from D, and so it is added
edges_3.add(D-E)
graph_3 = remove_edges(graph, edges_3)
candidate_3 = shortest_e(D, D) + Dijkstra(graph_3, D, F) // returns infinity
// Try without edge 2
edges_4 = [A-E]
if shortest_1 and shortest_2 share subpath up to node A:
// False because there are no edges in shortest_1 that go through A
// So no edges added
graph_4 = remove_edges(graph, edges_4)
candidate_4 = shortest_2(D, A) + Dijkstra(graph_4, A, F) // returns infinity
// Try without edge 3
edges_5 = [E-F]
if shortest_1 and shortest_2 share subpath up to node E:
// False because shortest_1 goes through D-E while shortest_2 goes through D-A-E
// So no edges added
graph_5 = remove_edges(graph, edges_5)
candidate_5 = shortest_2(D, E) + Dijkstra(graph_5, E, F)
shortest_3 = pick_shortest(candidate_2, candidate_3, candidate_4, candidate_5)
),实际上最终会选择从candidate_2
中找到的最短路径。同样,在下一次迭代(K = 4)中,我们将发现在迭代K = 3中实际上找到了第四个最短路径。如您所见,该算法正在预先完成工作,因此,在查找第K个最短路径的同时,它还在探索第K个最短路径之外的一些路径。因此,它不是第K条最短路径问题的最佳算法。 Eppstein算法可以做得更好,但是更复杂。
可以通过使用多个嵌套循环来概括上述方法。 Wikipedia page on Yen’s algorithm已经为更通用的实现提供了出色的伪代码,因此我将避免在此处编写它。请注意,维基百科代码使用列表graph_2
来保存每个最短路径,并使用列表A
来保存每个候选路径,并且候选最短路径会在循环迭代中持续存在。
备注
由于使用Dijkstra的算法,Yen的算法不能具有包含循环的Kth最短路径。当使用未加权的边缘时(如上面的示例),这不是很明显,但是如果添加了权重,则可能会发生这种情况。因此,Eppstein的算法考虑了循环,因此效果也更好。 This other answer包含link,可以很好地说明Eppstein的算法。
答案 3 :(得分:-1)
即使问题有一个有效的接受答案,这个答案通过提供样本工作代码来解决实施问题。在这里找到第k个最短路径的有效代码:
//时间复杂度:O(V k (V * logV + E))
using namespace std;
typedef long long int ll;
typedef short int i16;
typedef unsigned long long int u64;
typedef unsigned int u32;
typedef unsigned short int u16;
typedef unsigned char u8;
const int N = 128;
struct edge
{
int to,w;
edge(){}
edge(int a, int b)
{
to=a;
w=b;
}
};
struct el
{
int vertex,cost;
el(){}
el(int a, int b)
{
vertex=a;
cost=b;
}
bool operator<(const el &a) const
{
return cost>a.cost;
}
};
priority_queue <el> pq;
vector <edge> v[N];
vector <int> dist[N];
int n,m,q;
void input()
{
int i,a,b,c;
for(i=0;i<N;i++)
v[i].clear();
for(i=1;i<=m;i++)
{
scanf("%d %d %d", &a, &b, &c);
a++;
b++;
v[a].push_back(edge(b,c));
v[b].push_back(edge(a,c));
}
}
void Dijkstra(int starting_node, int ending_node)
{
int i,current_distance;
el curr;
for(i=0;i<N;i++)
dist[i].clear();
while(!pq.empty())
pq.pop();
pq.push(el(starting_node,0));
while(!pq.empty())
{
curr=pq.top();
pq.pop();
if(dist[curr.vertex].size()==0)
dist[curr.vertex].push_back(curr.cost);
else if(dist[curr.vertex].back()!=curr.cost)
dist[curr.vertex].push_back(curr.cost);
if(dist[curr.vertex].size()>2)
continue;
for(i=0;i<v[curr.vertex].size();i++)
{
if(dist[v[curr.vertex][i].to].size()==2)
continue;
current_distance=v[curr.vertex][i].w+curr.cost;
pq.push(el(v[curr.vertex][i].to,current_distance));
}
}
if(dist[ending_node].size()<2)
printf("?\n");
else
printf("%d\n", dist[ending_node][1]);
}
void solve()
{
int i,a,b;
scanf("%d", &q);
for(i=1;i<=q;i++)
{
scanf("%d %d", &a, &b);
a++;
b++;
Dijkstra(a,b);
}
}
int main()
{
int i;
for(i=1;scanf("%d %d", &n, &m)!=EOF;i++)
{
input();
printf("Set #%d\n", i);
solve();
}
return 0;
}