Dijkstra算法:
将所有点分为两个集合。如果源点s到u的最短路径已经确定,点u就属于集合Va,否则属于集合Vb。
1.将源点s到图中各点的直接距离当做初始值记录为s到各点的最短距离,不能到达的记为INF。S到S距离为0。
2.在集合Vb中的点中找一个点u,使得源点s到该点u路径长度最短,将u从Vb中除去,加到V1中。这时候求出了当前S到u的最短路径。
3.把新确定的点u更新s到集合Vb中每一个点v的距离,如果s到u的距离加上u到v的直接距离小于当前s到v的距离,则表示新找到的最短路径长度比之前的更短,那么更新这个距离,并更新最短路径。
4.重复步骤2.3,直到集合Vb中已经没有点,或是Vb没有从源点s能达到的点。
如果有带花费的最短路径,建立一个二维数组cost[][]表示花费图,建立一维数组Value[]来更新最小花费。则对于每个与k相邻的在Vb中的点j,更新s到j的最短距离时,如果距离相等,则更新花费。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 110;
const int INF = 1000000000;
int Map[MAXN][MAXN],pre[MAXN],Dist[MAXN];
bool vis[MAXN];
//Map[]来存储图,pre[]来保存结点前驱、源点、终点
//Dist[i]为源点s到节点i的最短距离
//vis[i]记录点i是否属于集合Va
void Dijkstra(int N,int s)
{
int Min;
for(int i = 1; i <= N; ++i) //初始化
{
vis[i] = false;
if(i != s)
{
Dist[i] = Map[s][i];
pre[i] = s;
}
}
Dist[s] = 0;
vis[s] = true;
for(int i = 1; i <= N-1; ++i) //循环N-1次,求源点s到其他N-1个点最短路径
{
Min = INF;
int k = 0;
for(int j = 1; j <= N; ++j) //在Vb中的点钟取一个s到其距离最小的点k
{
if(!vis[j] && Dist[j] < Min)
{
Min = Dist[j];
k = j;
}
}
if(k == 0) //如果没有点可以扩展,即剩余的点不可达,返回
return;
vis[k] = true; //将k从Vb重除去,加入到Va中
for(int j = 1; j <= N; ++j)
{ //对于每个与k相邻的在Vb中的点j,更新s到j的最短距离
if(!vis[j] && Map[k][j] != INF && Dist[j] > Dist[k] + Map[k][j])
{
Dist[j] = Dist[k] + Map[k][j];
pre[j] = k;
}
}
}
}
int main()
{
int N,M,a,b,w;
while(~scanf("%d%d",&N,&M) && (N||M))
{ //初始化注意
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= N; ++j)
Map[i][j] = INF;
//memet(Map,INF,sizeof(Map);这样是错的。不能这样子初始化。。。
memset(Dist,INF,sizeof(Dist));
memset(pre,0,sizeof(pre));
for(int i = 0; i < M; ++i)
{
scanf("%d%d%d",&a,&b,&w);
Map[a][b] = Map[b][a] = w;
}
Dijkstra(N,1);
printf("%d\n",Dist[N]);
}
return 0;
}
BellmanFord算法:
处理带负权边的单元最短路径问题。
1.将Dist[]赋值为INF,出发点为s,Dist[s] = 0。
2.对于每条边(u,v),如果Dist[u] != INF,且Dist[v] > Dist[u] + Map[u][v],则Dist[v] = Dist[u] + Map[u][v]。
3.重复步骤(2) N-1次或直到某次中不再更新,进入步骤(4)。
4.对于每条边(u,v),如果Dist[u] != INF,且Dist[v] > Dist[u] + Map[u][v],则存在负权回路。
如果寻找负权回路,将Dist[]赋值为INF,寻找最短路径。
如果寻找正权回路,将Dist[]赋值为0,寻找最长路径。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 550;
const int MAXM = 5500;
const int INF = 0xffffff0;
struct EdgeNode
{
int to;
int w;
int next;
}Edges[MAXM];
int Head[MAXN],Dist[MAXN];
bool BellmanFord(int N,int M)
{
Dist[1] = 0;
for(int i = 1; i < N; ++i)
{
for(int j = 1; j <= N; ++j)
{
if(Dist[j] == INF)
continue;
for(int k = Head[j]; k != -1; k = Edges[k].next) //寻找最短路径
{
if(Edges[k].w != INF && Dist[Edges[k].to] > Dist[j] + Edges[k].w)
Dist[Edges[k].to] = Dist[j] + Edges[k].w;
}
}
}
for(int j = 1; j <= N; ++j)
{
if(Dist[j] == INF)
continue;
//Dist[u] != INF 且Dist[v] > Dist[u] + Map[u][v],则存在负权回路。寻找正权回路,则改变符号为<,去掉Dist[u] != INF
for(int k = Head[j]; k != -1; k = Edges[k].next)
{
if(Edges[k].w != INF && Dist[Edges[k].to] > Dist[j] + Edges[k].w)
return true; //存在负权回路
}
}
return false; //不存在负权回路
}
int main()
{
int F,N,M,W,S,E,T;
scanf("%d",&F);
while(F--)
{
//初始化
memset(Dist,INF,sizeof(Dist));
memset(Head,-1,sizeof(Head));
memset(Edges,0,sizeof(Edges));
scanf("%d%d%d",&N,&M,&W);
int id = 0;
for(int i = 0; i < M; ++i)
{
scanf("%d%d%d",&S,&E,&T);
Edges[id].to = E;
Edges[id].w = T;
Edges[id].next = Head[S];
Head[S] = id++;
Edges[id].to = S;
Edges[id].w = T;
Edges[id].next = Head[E];
Head[E] = id++;
}
for(int i = 0; i < W; ++i)
{
scanf("%d%d%d",&S,&E,&T);
Edges[id].to = E;
Edges[id].w = -T;
Edges[id].next = Head[S];
Head[S] = id++;
}
if(BellmanFord(N,M))
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
SPFA算法:
在计算带负边权图的单源最短路径基础上,降低时间复杂度。
1.初始化Dist[] = INF,Dist[s] = 0,新建一个队列,将源点S入队,标记S已经在队列中。
2.入队首取出一个点top,标记top已经出队,对top出队的次数进行检查,如果大于N,说明出现负环,算法结束。否则遍历top所连接的边,如果边k的另一端节点to的距离可以更新,即Dist[Edges[k].b] > Dist[top] + Edges[k].w,则更新Dist[Edges[k].b] = Dist[top] + Edges[k].w,检查b是否在队列中,如果不在,加入队列。
3.重复步骤(2),直到队列为空。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXM = 1000100;
const int MAXN = 1000100;
struct EdgeNode
{
int to;
int w;
int next;
}Edges[2][MAXM];
int Head[2][MAXN],vis[MAXN],queue[MAXN],outque[MAXN];
__int64 Dist[MAXN];
//两个链式前向星,一个存正边,一个存反边(特殊题目要求,平常用一个),求1~N的最短路和N~1的最短路。
bool SPFA(int S,int N,int flag)
{
for(int i = 2; i <= N; ++i)
Dist[i] = 0xffffffff;
memset(vis,0,sizeof(vis));
memset(outque,0,sizeof(outque));
int iq = 0;
queue[iq++] = S;
vis[S] = 1;
Dist[S] = 0;
int i = 0,top,k;
while(i != iq)
{
top = queue[i];
vis[top] = 0;
outque[top]++;
if(outque[top] > N) //如果出队次数大于N,则说明出现负环
return false;
k = Head[flag][top];
while(k >= 0) //遍历top链接的边
{ //如果边k的另一端点to的距离可以更新,则更新
if(Dist[Edges[flag][k].to] - Edges[flag][k].w > Dist[top])
{
Dist[Edges[flag][k].to] = Dist[top] + Edges[flag][k].w;
if( !vis[Edges[flag][k].to]) //检查to是否在队列中,不在则加入队列
{
vis[Edges[flag][k].to] = 1;
queue[iq++] = Edges[flag][k].to;
}
}
k = Edges[flag][k].next;
}
i++;
}
return true;
}
int main()
{
int T,N,M,u,v,w;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&N,&M);
memset(Head,-1,sizeof(Head));
for(int i = 0; i < M; ++i)
{
scanf("%d%d%d", &u,&v,&w);
Edges[0][i].to = v;
Edges[0][i].w = w;
Edges[0][i].next = Head[0][u];
Head[0][u] = i;
Edges[1][i].to = u;
Edges[1][i].w = w;
Edges[1][i].next = Head[1][v];
Head[1][v] = i;
}
__int64 ans = 0;
SPFA(1,N,0);
for(int i = 1; i <= N; ++i)
if(Dist[i] != 0xffffffff)
ans += Dist[i];
SPFA(1,N,1);
for(int i = 1; i <= N; ++i)
if(Dist[i] != 0xffffffff)
ans += Dist[i];
printf("%I64d\n",ans);
}
return 0;
}