目录
求从s到t权值和最小的路径
Floyd-Warshall算法
- 求任意两点之间的最短路径(可以是负边)
- 时间复杂度 O ( n 3 ) O(n^{3}) O(n3)
- 该算法的思想十分简单, k k k表示中间桥梁,暴力遍历所有可能性,如果点 i i i到点 j j j的距离大于点 i i i到点 k k k的距离加上点 k k k到点 j j j的距离,那么就将 d [ i ] [ j ] d[i][j] d[i][j]更新为 d [ i ] [ k ] + d [ k ] [ j ] d[i][k]+d[k][j] d[i][k]+d[k][j],例如想要从昆明到重庆,如果从昆明到成都,再从成都到重庆的时间小于从昆明到重庆,那么显然使用时间少的来选择路径
模板:
const int MAX_V = 1005;
const int INF = 0x3f3f3f3f;
int dis[MAX_V][MAX_V];//dis[u][v]表示顶点u和v之间的最短路径,其中若没有连接则dis[u][v] = INF, u=v时,dis[u][v] = 0
int v;
void Floyd_Warshall() {
for(int k = 1; k <= v; k++)
for(int i = 1; i <= v; i++)
for(int j = 1; j <= v; j++)
if(i != j && i != k && j != k)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
Floyd例题
问题描述:
现在有一无向连通加权图,它有
N
N
N个顶点和
M
M
M条边,该图不包含自环以及重边,
其中第
i
(
1
<
=
i
<
=
M
)
i(1<=i<=M)
i(1<=i<=M)条边连接顶点
a
i
a_i
ai 和
b
i
b_i
bi,距离为
c
i
c_i
ci。
a
i
=
b
i
a_i = b_i
ai=bi的边表示自环,重边是两条边
(
(
a
i
,
b
i
)
=
(
a
j
,
b
j
)
((a_i,b_i)=(a_j,b_j)
((ai,bi)=(aj,bj)或者
(
a
i
,
b
i
)
=
(
b
j
,
a
j
)
)
(
1
<
=
i
<
=
j
<
=
M
)
(a_i,b_i)=(b_j,a_j))(1<=i<=j<=M)
(ai,bi)=(bj,aj))(1<=i<=j<=M)
连通图是指每对不同顶点之间都有一条路径的图。
请找到不包含在任意两点之间的最短路径中的边的数量。
输入
第一行输入
n
,
m
n, m
n,m
接下来
m
m
m行,每行
a
 
b
 
c
a\, b\, c
abc, 表示顶点
a
a
a与顶点
b
b
b距离为
c
c
c。
输出
不包含在任意两点之间的最短路径中的边的数量
样例输入
3 3
1 2 1
1 3 1
2 3 3
样例输出
1
策略分析:
用两个邻接矩阵记录一下图,一个用来跑floyd算法,另一个用来做比较,如果某两条边更新距离之后不和之前的距离相等,说明这条边不包含在最短路径之中#include <cstdio>
#include <algorithm>
#define MAXN 1005
#define INF 0x3f3f3f3f
int d[MAXN][MAXN], be[MAXN][MAXN];
int V, E;
void floyd() {
for(int k = 1; k <= V; k++)
for(int i = 1; i <= V; i++)
for(int j = 1; j <= V; j++)
d[i][j] = std::min(d[i][j], d[i][k] + d[k][j]);
}
int main() {
int a, b, c, cnt = 0;
scanf("%d %d", &V, &E);
for(int i = 1; i <= V; i++) { //初始化
for(int j = 1; j <= V; j++) {
if(i == j) {
d[i][j] = 0;
be[i][j] = 0;
}else {
d[i][j] = INF;
be[i][j] = INF;
}
}
}
for(int i = 0; i < E; i++) {
scanf("%d %d %d", &a, &b, &c);
d[a][b] = c;
d[b][a] = c;
be[a][b] = c;
be[b][a] = c;
}
floyd();
for(int i = 1; i <= V; i++)
for(int j = i+1; j <= V; j++)
if(be[i][j] != INF && d[i][j] != be[i][j])
cnt++;
printf("%d\n", cnt);
return 0;
}
Dijkstra算法
- 无负边的情况下使用
- 时间复杂度 O ( E l o g ( V ) ) O(Elog(_V)) O(Elog(V))
- 找到最短距离确定的顶点,从该顶点出发更新相邻顶点的最短距离
普通模板
#define MAX_V 10001
#define INF 0x3f3f3f3f
int dis[MAX_V]; //dis[i]表示从顶点s出发到达i的最短路径,若不存在则为INF
int cost[MAX_V][MAX_V]; // 邻接矩阵
bool used[MAX_V]; //表示该点是否使用过
inline void init() { //初始化
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i != j)
cost[i][j] = INF;
}
void dijkstra(int s) {
for(int i = 1; i <= n; i++)
dis[i] = INF;
dis[s] = 0;
while(1) {
int v = -1;
for(int i = 1; i <= n; i++) // 找到最下一次能够扩展的最近的顶点v
if(!used[i] && (v == -1 || dis[v] > dis[i]))
v = i;
if(v == -1) break;
used[v] = 1;
for(int i = 1; i <= n; i++) //松弛通过v顶点能够到达的所有最短路径
dis[i] = min(dis[i], dis[v] + cost[v][i]);
}
}
Dijkstra例题
问题描述:
罗老师被邀请参加一个舞会,是在城市n,而罗老师当前所处的城市为1,附近还有很多城市
2
∼
n
−
1
2\sim n-1
2∼n−1,有些城市之间没有直接相连的路,有些城市之间有直接相连的路,这些路都是双向的,当然也可能有多条。
现在给出直接相邻城市的路长度,罗老师想知道从城市
1
1
1到城市
n
n
n,最短多少距离。
输入
输入
n
,
m
n, m
n,m,表示
n
n
n个城市和
m
m
m条路;
接下来
m
m
m行,每行
a
 
b
 
c
a\, b\, c
abc, 表示城市
a
a
a与城市
b
b
b有长度为
c
c
c的路。
输出
输出 1 1 1到 n n n的最短路。如果 1 1 1到达不了 n n n,就输出 − 1 -1 −1。
样例输入
5 5
1 2 20
2 3 30
3 4 20
4 5 20
1 5 100
样例输出
90
#include <cstdio>
#include <algorithm>
#define MAX_V 10001
#define INF 0x3f3f3f3f
using namespace std;
int dis[MAX_V];
int cost[MAX_V][MAX_V];
bool used[MAX_V] = {0};
int n, m, s;
inline void init() {
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i != j)
cost[i][j] = INF;
}
void dijkstra(int s) {
for(int i = 1; i <= n; i++)
dis[i] = INF;
dis[s] = 0;
while(1) {
int v = -1;
for(int i = 1; i <= n; i++)
if(!used[i] && (v == -1 || dis[v] > dis[i]))
v = i;
if(v == -1) break;
used[v] = 1;
for(int i = 1; i <= n; i++)
dis[i] = min(dis[i], dis[v] + cost[v][i]);
}
}
int main() {
int u, v, c;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++) {
scanf("%d%d%d", &u, &v, &c);
cost[u][v] = min(cost[u][v], c); //如果u,v之间有多条路,只考虑最短的一条即可
cost[v][u] = cost[u][v];
}
dijkstra(1);
if(dis[n] != INF)
printf("%d\n", dis[n]);
else
printf("%d\n", -1);
return 0;
}
链式前向星优化
struct Edge {
int to, next, w;
}edge[MAXX];
int cnt, n, m, s, head[MAXN], dis[MAXN]; //表示n个顶点,m条边,要初始化head,dis
bool vst[MAXN];
inline void addEdge(int u, int v, int w) {
edge[++cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
void dijkstra(int s) {
dis[s] = 0;
int cur = s;
while(!vst[cur]) {
vst[cur] = 1;
for(int i = head[cur]; i != -1; i = edge[i].next) {
if(!vst[edge[i].to] && dis[edge[i].to] > dis[cur] + edge[i].w)
dis[edge[i].to] = dis[cur] + edge[i].w;
}
int mn = INF;
for(int i = 1; i <= n; i++) {
if(!vst[i] && mn > dis[i]) {
mn = dis[i];
cur = i;
}
}
}
}
链式前向星 + 堆优化
模板题
问题描述:
给定一个
N
N
N 个点,
M
M
M 条有向边的带非负权图,请你计算从
S
S
S 出发,到每个点的距离。
数据保证你能从
S
S
S 出发到任意点。
输入
第一行为三个正整数
N
,
M
,
S
N, M, S
N,M,S。 第二行起
M
M
M 行,每行三个非负整数
u
i
,
v
i
,
w
i
u_i, v_i, w_i
ui,vi,wi,表示从
u
i
u_i
ui到
v
i
v_i
vi有一条权值为
w
i
w_i
wi的边。其中
1
≤
N
≤
100000
;
1\leq N\leq 100000;
1≤N≤100000;
1
≤
M
≤
200000
1 \leq M \leq 200000
1≤M≤200000
输出
输出一行 N N N个空格分隔的非负整数,表示 S S S 到每个点的距离。
样例输入
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
样例输出
0 2 4 3
#include <iostream>
#include <cstring>
#include <queue>
#define MAXN 100005
#define MAXX 200005
#define INF 0x3f3f3f3f
#define P pair<int, int>
using namespace std;
struct Edge {
int next, to, cost;
}edge[MAXX];
bool vst[MAXN];
int dis[MAXN], head[MAXN], n, m, s, cnt;
priority_queue <P, vector<P >, greater<P > > q; //从小到大排
void add_edge(int u, int v, int cost) { //链式前向星存边
edge[++cnt].to = v;
edge[cnt].next = head[u];
edge[cnt].cost = cost;
head[u] = cnt;
}
void dijkstra() {
dis[s] = 0;
q.push(P(0, s));
while(!q.empty()) {
int cur = q.top().second;
q.pop();
if(!vst[cur]) {
vst[cur] = 1;
for(int i = head[cur]; i != -1; i = edge[i].next) { //访问当前顶点cur能到达的所有顶点
int u = edge[i].to;
if(dis[u] > dis[cur] + edge[i].cost) {
dis[u] = dis[cur] + edge[i].cost;//松弛操作
q.push(P(dis[u], u));//加入堆中
}
}
}
}
}
int main() {
ios::sync_with_stdio(0);
int u, v, w;
cin >> n >> m >> s;
memset(dis, INF, sizeof(dis)); //一定要初始化
memset(head, -1, sizeof(head));
for(int i = 1; i <= m ; i++) {
cin >> u >> v >> w;
add_edge(u, v, w);
}
dijkstra();
for(int i = 1; i <= n; i++)
cout << dis[i] << " ";
return 0;
}
Bellman-Ford算法
- 用于求解固定顶点到其他顶点的的最短路径问题
- 时间复杂度 O ( E V ) O(EV) O(EV)
- 可用来检查负回路(如果存在一个环从自己出发又回到自己,而且这个环上的权值之和为负数,就叫负权环或负权回路)
模板
const int maxx = 200;
const int INF = 999999;
struct edge {
int from;//来自哪个顶点
int to;//指向哪个顶点
int cost;//权值
};
edge es[maxx];//存储边的信息的数组
int d[maxx];//存储距离的数组
int v, e;//顶点和边
//求解从顶点s出发到所有其他的最短距离
void shortese_path(int s) {
for(int i = 0; i < v; i++) d[i] = INF;//初始化距离
d[s] = 0;//起点到起点的距离为0
while(1) {//循环最多执行v-1次,每执行一次,更新顶点到所有可连接的顶点的最短路径
bool update = false;
for(int i = 0; i < e; i++) {//遍历所有可能的边,进行松弛
edge edg = es[i];
if(d[edg.from] != INF && d[edg.to] > d[edg.from] + edg.cost) {//如果新的顶点的距离大于当前路径,则更新为短的当前路径
d[edg.to] = d[edg.from] + edg.cost;
update = true;
}
}
if(!update) //当所有的路径无法再更新的时候
break;
}
}
//求解从顶点s出发是否存在负圈
bool find_negative_loop(int s) {
memset(d, 0, sizeof(d));//将所有距离置为0
for(int i = 0; i < v; i++) {//从第i个顶点出发
for(int j = 0; j < e; j++) {//每个顶点遍历所有的边
edge edg = es[j];
if(d[edg.to] > d[edg.from] + edg.cost) {
d[edg.to] = d[edg.from] + edg.cost;
if(i == v-1)//循环执行了v次
return true;//有负圈
}
}
}
return false;//无负圈
}
同时还有队列优化的Bellman-Ford算法(SPFA)就不写了(多用dijkstra)
算法思想对比
首先简单进行下对比:
算法 | floyd | dijkstra | Bellman-Ford |
---|---|---|---|
空间复杂度 | O ( n 2 ) O(n^2) O(n2) | O ( E ) O(E) O(E) | O ( E ) O(E) O(E) |
时间复杂度 | O ( n 3 ) O(n^3) O(n3) | O ( ( V + E ) l o g V ) O((V+E)logV) O((V+E)logV) | O ( E V ) O(EV) O(EV) |
使用 | 稠密图,顶点较少 | 稠密图 | 稀疏图 |
可否处理负权 | 可以 | 不能 | 可以 |
可否判断负权 | 不能 | 不能 | 可以 |
对于这三种基本的算法
floyd
算法就是通过循环的方式,暴力枚举每两个点之间的路,是否存在着更短的路径,因此效率比较低。dijkstra
算法的思想是一种贪心的思想,每次都从剩余未访问过的顶点找到距离起点最近的点,加入队列,然后更新所有能够通过这个点访问到的顶点的距离,并且把这个点标记成已经访问过,后面不再访问它。(这样做是因为每一步都是从起点出发,并且以最短的路径来更新的,所以已经访问过的点不需要再次访问,起点到它的距离一定是最短的。)然后重复这个过程,直到所有点都被访问过结束。Bellman-Ford
算法会进行 V − 1 V-1 V−1次松弛(松弛就是用所有节点更新距离最短)来找到最短路径,每次都利用所有的顶点来进行松弛操作,直到无法松弛为止。而这个算法能够判负环的原因就在于,每一次松弛至少会确定一个顶点的最短路径,但不知道是哪一个顶点,因此最多进行 V − 1 V-1 V−1次松弛,如果松弛的次数超过了这个值,就可以说明有负环(对于负环来讲,环上权值之和为负,必然会无限的迭代下去)
对于这三种算法,针对不同的情况使用,dijkstra
算法可扩展性强,效率较高。Bellman_Ford
可以判断负环,而floyd
算法很好写,针对特定情况很方便。