给定一个有向图求出里面所有节点对之间的最短路径。
问题的详细描述见Wikipedia:https://en.wikipedia.org/wiki/Shortest_path_problem。
介绍两个算法 O(V3) 的Floyd算法和 O(V2lgV+VE) 的Jhonson算法。分别应对稠密图和稀疏图的情况。
这是一个动态规划算法。设 dkij 是 i,j 之间所有中间节点全部取自 <1,2,...,i,...,k> 的一条最短路的权重。则状态转移方程如下
dkij={wijmin{dk−1ij,dk−1ik+dk−1ij}x=0x!=0
伪代码
for k=1 to |V|
for i=1 to V
for j=1 to V
d[i][j]=min{d[i][j],d[i][k]+d[k][j]}
c++代码
void floyd(int n)
{
memcpy(d,w,sizeof(w));//初始化d(0)
for(int k=1 ; k<=n ; ++k)
for(int i= 1 ; i<=n ; ++i)
for(int j=1 ; j<=n ; ++j)
d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
}
Johnson算法的核心思想是直接对每个顶点做一次Dijkstra,这样时间复杂度只有 O(VElgV) (用斐波那契堆实现只需 O(V2lgV) )对于稀疏图来说是会渐进优于Floyd算法的,但是我们知道Dijkstra算法只能用于权重为正数的情况。所以要对图上的权重进行重新映射一次。
重塑权重值
设 p:<v0,...,vi,...,vk> 为从 v0 到 vk 的一条最短路,则重塑的权重 w′ 必须满足两个条件:
1、 w(p)=δ(v0,vk)⇔w′(p)=δ(v0,vk)
2、 w(p) 不包含负环 ⇔ w′(p) 不包含负环
下面证明取权重映射 w′(u,v)=w(u,v)+h(u)−h(v) 时满足条件。
w′(p)=∑ni=1w′(vi,vi−1)
=∑ni=1w(vi,vi−1)+h(v0)−h(vk)
=w(p)+h(v0)−h(vk)
第一条肯定满足了,因为 h(v0),h(vk) 是预处理出来的常数,第二条,当 p 为一负权重的环路时, v0=vk ,所以 w′(p)=w(p) 也是负环。
构造函数 h
我们采取的方法是这样的,添加一新节点编号为0,到每一个顶点的距离为0,然后我们令 h(v)=δ(0,v) ,由三角不等式 h(v)≤h(u)+w(u,v) ,所以 w′(u,v)≥0
简单写一下伪代码
伪代码
compute G′
spfa(G’,0)
for each vertex v ∈G.V
h(v)=δ(0,v)
for each edge (u,v)∈G.E
w′(u,v)=w(u,v)+h(u)−h(v)
for each vertex v ∈G.V
Dijkstra(G,v)
for i=1 to V
forj=1 to V
d[i][j]=d[i][j]+h[j]−h[i]
c++代码
void spfa(int s)
{
for(int i=1 ; i<=nv ; ++i)d[s][i] = INF;
d[s][s] = 0;
memset(inq,false,sizeof(inq));
queue<int> q;
q.push(s);
inq[s] = true;
while(!q.empty())
{
int u =q.front(); q.pop();
inq[u] = false;
for(int i=first[u] ; i!=-1 ; i = nt[i])
{
Edge &e = edges[i];
if(d[s][e.to]>d[s][u]+e.weight)
{
d[s][e.to]=d[s][u]+e.weight;
if(!inq[e.to]){
q.push(e.to);inq[e.to] = true;
}
}
}
}
}
void dijkstra(int s)
{
bool vis[MAX_V];
memset(vis,false,sizeof(vis));
for(int i=1 ; i<=nv ; ++i)d[s][i] = INF;
d[s][s] = 0;
priority_queuevector,greater > q;
q.push(pii(0,s));
while(!q.empty())
{
int u = q.top().second;q.pop();
if(vis[u])continue;
else vis[u] = true;
for( int i=first[u] ; i!=-1 ; i = nt[i])
{
Edge& e = edges[i];
if(d[s][e.to]>d[s][u]+e.weight)
{
q.push(pii(d[s][e.to],e.to));
d[s][e.to] = d[s][u]+e.weight ;
}
}
}
}
void compute_Go(int last_edge)//最后一条边编号
{
int id = last_edge+1;
for(int i=1 ; i<=nv ;++i )
{
read_edge(0,i,0,id);//向边集数组添加新边
id++;
}
}
void johnson()
{
int h[MAX_V];
compute_Go(ne);
spfa(0);
for(int i=1 ; i<=nv ;++i)h[i] = d[0][i];
//重塑边权重
for(int i=1 ; i<=ne ;++i)
{
Edge &e = edges[i];
e.weight = e.weight+h[e.from]-h[e.to];
}
for(int i=1 ; i<=nv ; ++i)
{
dijkstra(i);
}
//映射回原来的最短路径
for(int i=1 ;i<=nv ;++i)
{
for(int j=1 ; j<=nv ; ++j)
d[i][j] = d[i][j]+h[j]-h[i];
}
}
Johnson算法实现太复杂,在V不是很大的时候都建议用floyd。
代码测试题poj 1125。。。。就是water problem了。。。