1, Dijkstra算法
(1) 处理正边权…可以处理负边权,但必须是负边只存在源点s连出去的边
(2) 时间复杂度……O(V^2)
例题:
In: 输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商 店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1& lt;=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
(输入保证至少存在1条商店到赛场的路线。)
Out: 对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间
Code:
#include<iostream>
#include<stdio.h>
#include<iomanip>
using namespace std;
#define N 10000
#define MAX 100000099
int a[N][N];
int dist[N];
void input (int n,int m)
{
int p,q,len,i,j;
for( i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
a[i][j]=MAX;
dist[i]=MAX;
}
for(i=0;i<m;i++)
{
cin>>p>>q>>len;
if(len<a[p][q])
{
a[p][q]=len;
a[q][p]=len;
}
}
}
void dijkstra(int n)
{
int s[N],newdist;
for(int i=1;i<=n;i++)
{
dist[i]=a[1][i];
s[i]=0;
}
dist[1]=0;
s[1]=1;
for(i=2;i<=n;i++)
{
int j,tem=MAX;
int u=1;
for(j=2;j<=n;j++)
if(!s[j]&&dist[j]<tem)
{
u=j;
tem=dist[j];
}
s[u]=1;
for(j=2;j<=n;j++)
{
if(!s[j]&&a[u][j]<MAX)
{
newdist=dist[u]+a[u][j];
if(newdist<dist[j])
dist[j]=newdist;
}
}
}
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m),m||n)
{
input(n,m);
dijkstra(n);
cout<<dist[n]<<endl;
}
return 0;
}
2.Bellman-Ford算法
如果原图中不存在负环:
从s可达的任意点v的最短路至多经过v–1条边。对于这v–1条边,第一次松弛后第一条边不可再松弛。第二次松弛后第二条边不可再松弛,…这样便证明了算法在没有负环的图上的正确性。
如果原图中存在负环:
显然对于负环中的任何一个点,它的最短路会是负无穷,所以松弛操作不会停止。算法返回false
时间复杂度: 两个for循环O(VE)
例题(hdu2544)
Code:
#include <stdio.h>
#define MAX 1000000000
int m, n, i, j ,k;
int start, end, value;
int dis[105], edge[105][105];
int Min (int a, int b)
{
return a < b ? a : b;
}
void Bellman (int source)
{
for (i = 0; i <= n; ++i)
{
dis[i] = edge[1][i];
}
for (k = 2; k < n; ++k)
{
for (i = 1; i <= n; ++i)
{
for (j = 1; j <= n; ++j)
{
dis[i] = Min(dis[i], dis[j] + edge[j][i]);
}
}
}
printf ("%d ", dis[n]);
}
int main (void)
{
while (scanf ("%d%d", &n, &m) && (m || n))
{
for (i = 0; i <= n; ++i)
{
for (j = 0; j <= n; ++j)
{
edge[i][j] = MAX;
}
}
for (i = 0; i < m; ++i)
{
scanf ("%d%d%d", &start, &end, &value);
edge[start][end] = edge[end][start] = value;
}
Bellman (1);
}
return 0;
}
3.SPFA算法
一个Bellman-Ford算法优化: 每次只用距离减小的点去更新其他点
如何实现?队列!
思路:
Step 1:初始时所有点d[]值置INF,源点d[]为0。将源点放进队列。
Step 2:当队列不为空时每次从队列中取出队首,对队首的每条边进行松弛。将松弛后d[]值改变并且不在队列中的点加入队列
两点说明:
时间复杂度
最坏 : O(VE)一般 : O(kE)。
如何判负环?
对每个点记录一个num值,表示被更新了多少次,如果某个点被更新的次数超过n-1次,则有负环
例题(1544)
Code:
#include <stdio.h>
#define MAX 1000000000
struct Edge
{
int start;
int end;
int next;
int value;
} eg[20005];
int m, n, i, pot;
int startNode, endNode, value;
int first, tail, popNumber;
int dis[105], head[105], queue[30000];
bool chose[200];
void Build (int st, int nd, int val)
{
eg[pot].start = st;
eg[pot].end = nd;
eg[pot].value = val;
eg[pot].next = head[st];
head[st] = pot++;
}
void BFS (int x)
{
int k;
for (k = head[x]; k != -1; k = eg[k].next)
{
if (dis[eg[k].end] > dis[x] + eg[k].value)
{
dis[eg[k].end] = dis[x] + eg[k].value;
if (chose[eg[k].end] == false)
{
queue[tail++] = eg[k].end;
chose[eg[k].end] = true;
}
}
}
}
void SPFA (void)
{
dis[1] = 0;
first = tail = 0;
queue[tail++] = 1;
//printf ("first = %d ", queue[first]);
chose[1] = true;
while (tail - first != 0)
{
//printf ("first = %d ", queue[first]);
popNumber = queue[first];
chose[queue[first]] = false;
++first;
BFS (popNumber);
}
printf ("%d ", dis[n]);
}
int main (void)
{
while (scanf ("%d%d", &n, &m) && (m || n))
{
for (i = 0; i <= n; ++i)
{
head[i] = -1;
dis[i] = MAX;
chose[i] = false;
}
pot = 0;
for (i = 0; i < m; ++i)
{
scanf ("%d%d%d", &startNode, &endNode, &value);
Build (startNode, endNode, value);
Build (endNode, startNode, value);
}
SPFA ();
}
return 0;
}
4.Floyd算法
时间复杂度: 三重for循环 O(V^3)
先看代码:
for(k=1;k<=V;k++)
for(i=1;i<=V;i++)
for(j=1;j<=V;j++)
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j]; 实际上是一个精巧的DP!
DP过程:
设dis[i][j][k]表示从i到j的路径中,经过的点的编号不超过k的最短路
边界条件:dis[i][j][0] = dis[i][j]
转移方程:
dis[i][j][k] = Min(dis[i][j][k-1] , dis[i][k][k-1] + dis[k][j][k-1])
(k > 0 , 0 <= i , j <= n)
答案:dis[i][j][n]
例题(1544)
Code:
#include <stdio.h>
int i, j, k, m, n, start, end, value;
int map[200][200], dis[200][200];
const int MAX = 1000000000;
int Min (int a, int b)
{
return a < b ? a : b;
}
int main (void)
{
while (scanf("%d%d", &n, &m) && (m || n))
{
for (i = 0; i <= n; ++i)
{
for (j = 0; j <= n; ++j)
{
map[i][j] = MAX;
}
}
for (i = 0; i < m; ++i)
{
scanf ("%d%d%d", &start, &end, &value);
map[start][end] = map[end][start] = value;
}
for (k = 1; k <= n; ++k)
{
for (i = 1; i <= n; ++i)
{
for (j = 1; j <= n; ++j)
{
map[i][j] = Min (map[i][j], map[i][k] + map[k][j]);
}
}
}
printf ("%d ", map[1][n]);
}
return 0;
}
<01>最短路算法的应用
求无向图最小环
最小环是指一个图中一个至少包含三个顶点的边权和最小的环
Floyd!
设某环编号最大的顶点为 L 另两个顶点编号分别为 M 和 N (M,N < L),则最大编号为 L 的最小环长度即为mp(M,L) + mp(N,L) + dp(M,N) ,其中 dp(M,N) 表示以 0…L-1 号顶点为中间点时的最短路径,刚好符合 Floyd 算法最外层循环到 k=L 时的情况。
<02>若为有向图呢?(CDOJ 1329)
最短路算法的应用
求下面不等式组的一组解:
X1 - X2 <= 0 X1 - X5 <= -1 X2 - X5 <= 1 X3 - X1 <= 5 X4 - X1 <= 4 X4 - X3 <= -1 X5 - X3 <= -3 X5 - X4 <= -3
Xi<=0(i=1,2,3,4,5)
求完最短路后图中每条边
dis(v) <= dis(u) + w(u, v)
对于不等式Xi - Xj <= c,把它化成不等式:Xi <= Xj + c,就可以化成边Vj -> Vi,权值为c。最后,我们在这张图上求一次单源最短路径,这些不等式就会全部都满足
<03>最短路算法的应用
求下面不等式组的一组解:
X1 - X2 <= 0 X1 - X5 <= -1 X2 - X5 <= 1 X3 - X1 <= 5 X4 - X1 <= 4 X4 - X3 <= -1 X5 - X3 <= -3 X5 - X4 <= -3
Xi<=0(i=1,2,3,4,5)
对于最后一个Xi<=0怎么办?
新添一个X0=0,并使Xi-X0<=0!
最后以X0为源点,跑最短路
最后每个点的dis值就为答案
注意:由于有负边权,不能使用dijkstra