最短路问题汇总
注意,这里为了方便描述算法,所以都用了最易理解的邻接矩阵来写,比赛中为了追求效率,一般将邻接矩阵改为链式前向星或者邻接表。
迪杰斯特拉算法 O(V^2)
1const int MAXN = 100;
2const int INF = 0x3f3f3f3f;
3// 有向无环图 DAG
4int V, E; // 顶点数和边数
5int graph[MAXN][MAXN]; // DAG邻接矩阵,初始值为INF,不可达为INF,否则为cost值
6int d[MAXN]; // 从某点s出发到其它任意结点的最短路径长度,初始值为INF
7int visited[MAXN]; // 某点是否访问过,访问过则为1否则为0
8
9// 初始化图
10void init() {
11 memset(graph, 0x3f, sizeof(graph));
12 cin >> V >> E;
13 int from, to, cost;
14 for (int i = 0 ; i < E; i++) {
15 cin >> from >> to >> cost;
16 graph[from][to] = cost;
17 }
18}
19// 迪杰斯特拉算法求解最短路,针对点展开
20void Dijkstra(int s) {
21 memset(d, 0x3f, sizeof(d));
22 memset(visited, 0, sizeof(visited));
23 visited[s] = 1;
24 for(int i = 0; i < V; i++) d[i] = graph[s][i];
25 d[s] = 0;
26 int k, min_cost;
27 // 无负边时最多更新n-1(其他结点数)次
28 for(int i = 0; i < V - 1; i++){
29 min_cost = INF;
30 // 寻找最未被访问的且权值最小的路径,需要优化的地方
31 for(int j = 0; j < V; j++){
32 if(!visited[j] && d[j] < min_cost){
33 k = j;
34 min_cost = d[j];
35 }
36 }
37 visited[k] = 1;
38 // 利用找到的结点更新最短路径
39 for(int j = 0; j < V; j++){
40 if(!visited[j] && min_cost + graph[k][j] < d[j]){
41 d[j] = min_cost + graph[k][j];
42 }
43 }
44 }
45}
迪杰斯特拉算法的堆优化 O(ElogV) 不含负权就用它 ~
1const int MAXN = 100;
2const int INF = 0x3f3f3f3f;
3// 有向无环图 DAG
4int V, E; // 顶点数和边数
5int graph[MAXN][MAXN]; // DAG邻接矩阵,初始值为INF,不可达为INF,否则为cost值
6int d[MAXN]; // 从某点s出发到其它任意结点的最短路径长度,初始值为INF
7int visited[MAXN]; // 某点是否访问过,访问过则为1否则为0
8
9typedef pair<int, int> P; // first: 最短距离 second:通往的顶点编号
10priority_queue<P, vector<P>, greater<P> > que; // greater<T> 从小到大取出
11
12// 初始化图
13void init() {
14 memset(graph, 0x3f, sizeof(graph));
15 cin >> V >> E;
16 int from, to, cost;
17 for (int i = 0 ; i < E; i++) {
18 cin >> from >> to >> cost;
19 graph[from][to] = cost;
20 }
21}
22// 迪杰斯特拉算法堆优化求解最短路,针对点展开
23void Dijkstra_optimise(int s) {
24 memset(d, 0x3f, sizeof(d));
25 memset(visited, 0, sizeof(visited));
26 // for(int i = 0; i < V; i++) d[i] = graph[s][i];
27 // 开始min_cost为0,若加上这一步,0 + graph[k][i] == d[i] == graph[k][i],导致无法更新队列
28 d[s] = 0;
29 int k, min_cost;
30 que.push(P(0, s));
31 while (!que.empty()) {
32 P p = que.top(); que.pop();
33 // 通过堆优化直接获得了目标
34 min_cost = p.first;
35 k = p.second;
36 // 若已经有更短的路径则不更新
37 if (min_cost > d[k]) continue;
38 visited[k] = 1;
39 for (int i = 0; i < V; i++) {
40 if (!visited[i] && min_cost + graph[k][i] < d[i]) {
41 d[i] = graph[k][i] + min_cost;
42 que.push(P(d[i], i));
43 }
44 }
45 }
46}
贝尔曼福德算法 O(VE)
1using namespace std;
2const int MAXN = 100;
3const int MAXE = 100;
4const int INF = 0x3f3f3f3f;
5// 有向无环图 DAG
6struct Edge {
7 int from, to, cost;
8};
9int V, E; // 顶点数和边数
10int d[MAXN]; // 从某点s出发到其它任意结点的最短路径长度,初始值为INF
11Edge edge[MAXE]; // 边集
12
13// 初始化图
14void init() {
15 cin >> V >> E;
16 int from, to, cost;
17 for (int i = 0 ; i < E; i++)
18 cin >> edge[i].from >> edge[i].to >> edge[i].cost;
19}
20// 贝尔曼福德算法求解最短路,针对边展开
21void Bellman_Ford(int s)
22{
23 memset(d, 0x3f, sizeof(d));
24 d[s] = 0;
25 while (1) {
26 int flag = 0;
27 // DAG下最少更新E次
28 for (int i = 0; i < E; i++) {
29 Edge e = edge[i];
30 if (d[e.from] != INF && d[e.to] > d[e.from] + e.cost) {
31 d[e.to] = d[e.from] + e.cost;
32 flag = 1;
33 }
34 }
35 // 如果没出现更新则退出
36 if (!flag) break;
37 }
38}
贝尔曼福德算法优化(SPFA + 优先队列)含负边就用它~
虽然大家都说SPFA已死,但其实是说笑罢了,含负权边或者负环的题还真离不开这东西~
1const int MAXN = 100;
2const int INF = 0x3f3f3f3f;
3// 有向无环图 DAG
4int V, E; // 顶点数和边数
5int graph[MAXN][MAXN]; // DAG邻接矩阵,初始值为INF,不可达为INF,否则为cost值
6int d[MAXN]; // 从某点s出发到其它任意结点的最短路径长度,初始值为INF
7int vis[MAXN]; // 结点是否已入队
8int push_count[MAXN]; // 结点入队次数,若大于等于V,则证明存在负环
9priority_queue<int, vector<int>, greater<int> > que; // greater<T> 从小到大取出
10
11// 初始化图
12void init() {
13 memset(graph, 0x3f, sizeof(graph));
14 cin >> V >> E;
15 int from, to, cost;
16 for (int i = 0 ; i < E; i++) {
17 cin >> from >> to >> cost;
18 graph[from][to] = cost;
19 }
20}
21// SPFA算法求解最短路
22void SPFA(int s) {
23 memset(d, 0x3f, sizeof(d));
24 memset(vis, 0, sizeof(vis));
25 memset(push_count, 0, sizeof(push_count));
26 d[s] = 0;
27 que.push(s);
28 vis[s] = 1;
29 push_count[s]++;
30 while (!que.empty()) {
31 int flag = 0; // 负环判断
32 int v = que.top(); que.pop(); vis[v] = 0;
33 for (int i = 0; i < V; i++) {
34 if (d[v] + graph[v][i] < d[i]) {
35 d[i] = d[v] + graph[v][i];
36 if (!vis[i]) {
37 que.push(i);
38 vis[i] = 1;
39 push_count[i]++;
40 if (push_count[i] >= V) {
41 flag = 1;
42 break;
43 }
44 }
45 }
46 }
47 if (flag) {
48 cout << "有负环" << endl;
49 break;
50 }
51 }
52}
弗洛伊德算法 O(V^3) 多源问题可以考虑用它~
1const int MAXN = 100;
2const int INF = 0x3f3f3f3f;
3// 有向无环图 DAG
4int V, E; // 顶点数和边数
5int graph[MAXN][MAXN]; // DAG邻接矩阵,初始值为INF,不可达为INF,否则为cost值
6int d[MAXN][MAXN]; // 从任意结点出发到其它任意结点的最短路径长度,初始值为INF
7
8// 初始化图
9void init() {
10 memset(graph, 0x3f, sizeof(graph));
11 cin >> V >> E;
12 int from, to, cost;
13 for (int i = 0 ; i < E; i++) {
14 cin >> from >> to >> cost;
15 graph[from][to] = cost;
16 }
17}
18// 弗洛伊德算法求解最短路
19void Floyd()
20{
21 // 初始化矩阵d
22 memset(d, 0x3f, sizeof(d));
23 for(int i = 0; i < V; i++)
24 for(int j = 0; j < V; j++)
25 d[i][j] = graph[i][j];
26 for(int k = 0; k < V; k++){//以顶点k为中转
27 for(int i = 0; i < V; i++){//从顶点i到顶点j
28 for(int j = 0; j < V; j++){
29 if (d[i][j] > d[i][k] + d[k][j])
30 d[i][j] = d[i][k] + d[k][j];
31 }
32 }
33 }
34}
附:
链式前向星
1const int MAXN = 100;
2struct Edge {
3 int to; // 边的终点
4 int next; // 与该边同起点的下一条边的编号
5 int w; // 边的权值
6};
7Edge edge[MAXN]; // 所有边的集合
8int head[MAXN]; // 所有起点的第一条边的编号,初始化为-1
9int V, E;
10// 建立图
11void build() {
12 int from, to, w, cnt = 0;
13 for (int i = 0; i < E; i++) {
14 cin >> from >> to >> w;
15 edge[cnt].to = to;
16 edge[cnt].w = w;
17 edge[cnt].next = head[from];
18 // 因此可将head初始化为-1,当next为-1时代表以该点为起点的边访问结束
19 head[from] = cnt++;
20 }
21}
22// 遍历整个图
23void traverse() {
24 for (int i = 0; i < V; i++) {
25 for (int j = head[i]; j != -1; j = edge[j].next) {
26 cout << " from " << i << " to " << edge[j].to << " weight: " << edge[j].w << endl;
27 }
28 }
29}
30// 遍历某个结点的邻接点
31void traverse_v(int v) {
32 for (int i = head[v]; i != -1; i = edge[i].next) {
33 cout << " from " << v << " to " << edge[i].to << " weight: " << edge[i].w << endl;
34 }
35}