1一、Dijkstra 单源最短路
21. 前提条件
3
4Dijkstra 用来求:从一个起点 start 到所有点的最短距离
5
6要求:边权不能为负数
7
8适合:
9
10带权图
11无负权边
12单源最短路
13
142. 核心概念
15
16维护两个数组:dist[i]
17
18表示:从 start 到 i 的当前最短距离
19
20visited[i]
21
22表示:i 这个点的最短路是否已经确定
23
24每一轮做两件事:
25
261. 从所有未确定的点里,找 dist 最小的点 u
272. 用 u 去更新它的邻接点 v
28
29更新公式:
30
31if(dist[v] > dist[u] + w)
32{
33 dist[v] = dist[u] + w;
34}
35
36这一步叫:松弛 relax
37
383. 图结构:邻接表
39#include <stdio.h>
40#include <stdlib.h>
41
42#define MAXN 1000
43#define INF 1000000000
44
45struct EdgeNode {
46 int to;
47 int weight;
48 struct EdgeNode* next;
49};
50
51struct EdgeNode* adj[MAXN];
52
53初始化:
54
55void initGraph(int n)
56{
57 for(int i = 0; i < n; i++)
58 {
59 adj[i] = NULL;
60 }
61}
62
63加有向边:
64
65void addDirectedEdge(int u, int v, int w)
66{
67 struct EdgeNode* node =
68 malloc(sizeof(struct EdgeNode));
69
70 node->to = v;
71 node->weight = w;
72 node->next = adj[u];
73 adj[u] = node;
74}
75
76加无向边:
77
78void addUndirectedEdge(int u, int v, int w)
79{
80 addDirectedEdge(u, v, w);
81 addDirectedEdge(v, u, w);
82}
83
844. Dijkstra
85void dijkstra(int n, int start, int dist[])
86{
87 int visited[MAXN];
88
89 for(int i = 0; i < n; i++)
90 {
91 dist[i] = INF;
92 visited[i] = 0;
93 }
94
95 dist[start] = 0;
96//这里先全部初始化,把最短路径先都设置为无穷,然后全部未遍历都是0,起始位置设置为0,起始到自己距离为0
97 for(int step = 0; step < n; step++)
98 {
99 int u = -1;
100
101 for(int i = 0; i < n; i++)
102 {
103 if(!visited[i] &&
104 (u == -1 || dist[i] < dist[u]))
105 {
106 u = i;
107 }
108 }
109//这里第一次就是找到start对应的位置之后停止,之后的循环需要找每次相邻的dist最小的值开始确定最小的i给到u,
110//step 控制轮数
111//u 每一轮清空给-1
112//内层 for 每一轮重新找当前 dist 最小且没 visited 的点。也就是遇到visited遍历过的跳过到后面,找未遍历的
113 if(u == -1 || dist[u] == INF)
114 {
115 break;
116 }
117//这里是如果找不到相邻的了,或者直接断开了,就停止循环了
118 visited[u] = 1;
119
120 struct EdgeNode* cur = adj[u];
121
122 while(cur != NULL)
123 {
124 int v = cur->to;
125 int w = cur->weight;
126
127 if(!visited[v] &&
128 dist[v] > dist[u] + w)
129 {
130 dist[v] = dist[u] + w;
131 }
132//这里就是找到相邻的最短的路径更新到dist里面
133 cur = cur->next;
134 }
135 }
136}
137
1385. 代码怎么理解
139
140这段:
141
142int u = -1;
143
144for(int i = 0; i < n; i++)
145{
146 if(!visited[i] &&
147 (u == -1 || dist[i] < dist[u]))
148 {
149 u = i;
150 }
151}
152
153意思是:
154
155从所有还没确定最短路的点中,
156找 dist 最小的点。
157
158这段:
159
160if(u == -1 || dist[u] == INF)
161{
162 break;
163}
164
165意思是:
166
167剩下的点已经到不了了,
168直接结束。
169
170这段:
171
172if(dist[v] > dist[u] + w)
173{
174 dist[v] = dist[u] + w;
175}
176
177意思是:
178
179如果 start -> u -> v
180比原来的 start -> v 更短,
181就更新 dist[v]。
182
1836. 例子
184
185图:
186
1870 --2-- 1
188| |
1895 1
190| |
1912-------
192
193边:
194
1950-1 权值2
1960-2 权值5
1971-2 权值1
198
199从 0 开始。
200
201初始:
202
203dist[0]=0
204dist[1]=INF
205dist[2]=INF
206
207选 0:
208
209更新 1:dist[1]=2
210更新 2:dist[2]=5
211
212现在:
213
214dist = [0,2,5]
215
216选 1:
217
218通过 1 到 2:
219dist[2] = min(5, 2+1) = 3
220
221现在:dist = [0,2,3]
222
223选 2,结束。
224
225答案:
226
2270 到 0 = 0
2280 到 1 = 2
2290 到 2 = 3
230
2317. Dijkstra 总结
232Dijkstra
233
234解决:
235 单源最短路
236
237条件:
238 边权 >= 0
239
240核心:
241 每次选 dist 最小且未确定的点
242
243更新:
244 dist[v] = min(dist[v], dist[u] + w)
245
246二、最小生成树 MST
247
2481. 前提条件
249
250最小生成树只针对:无向连通带权图
251
252目标:
253
254选出一些边,把所有点连起来,并且总权值最小
255
256要求:
257
2581. 所有点连通
2592. 没有环
2603. 边数是 n-1
2614. 总边权最小
2622. 生成树是什么
263
264如果有 n 个点,那么生成树一定有:
265
266n - 1 条边
267
268例如 4 个点,生成树有 3 条边。
269
270少于 n-1 条边,可能连不起来
271多于 n-1 条边,一定有环
272
2733. 最小生成树和最短路区别
274
275最短路:关心从一个点到另一个点的路径最短
276
277最小生成树:关心把所有点连起来总代价最小
278
279它们不是一个问题。
280
281三、Prim 算法
282
2831. 前提条件
284
285Prim 用来求:最小生成树
286
287适合:无向连通带权图
288
2892. 核心概念
290
291Prim 的思想:
292
293从一个点开始,逐渐扩大生成树。
294每次选择一条最便宜的边,把一个新点加入生成树。
295
296维护两个数组:lowCost[i]
297
298表示:当前生成树连接到 i 点的最小边权
299
300visited[i]
301
302表示:i 是否已经加入生成树
303
3043. 和 Dijkstra 最像的地方
305
306Prim 每轮也是:
307
308从未加入的点中,找 lowCost 最小的点 u。
309
310然后加入生成树:
311
312visited[u] = 1;
313
3144. 和 Dijkstra 最大区别
315
316Dijkstra 更新:
317
318dist[v] = dist[u] + w;
319
320意思:
321
322从 start 到 v 的路径长度
323
324Prim 更新:
325
326lowCost[v] = w;
327
328意思:
329
330生成树连到 v 的最小边权
331
332Prim 不关心路径总长度,只关心:
333把一个新点接进树,用哪条边最便宜
334
3355. Prim 代码
336int prim(int n, int start)
337{
338 int lowCost[MAXN];
339 int visited[MAXN];
340
341 for(int i = 0; i < n; i++)
342 {
343 lowCost[i] = INF;
344 visited[i] = 0;
345 }
346
347 lowCost[start] = 0;
348//还是初始化之后把start对应的花费设为0
349 int total = 0;
350
351 for(int step = 0; step < n; step++)
352 {
353 int u = -1;
354
355 for(int i = 0; i < n; i++)
356 {
357 if(!visited[i] &&
358 (u == -1 || lowCost[i] < lowCost[u]))
359 {
360 u = i;
361 }
362 }
363//这里还是每一轮找花费最小的元素
364 if(u == -1 || lowCost[u] == INF)
365 {
366 return -1;
367 }
368
369 visited[u] = 1;
370//被认定遍历过之后就加入生成树
371 total += lowCost[u];
372
373 struct EdgeNode* cur = adj[u];
374
375 while(cur != NULL)
376 {
377 int v = cur->to;
378 int w = cur->weight;
379//这里不是对应要求最短路径,只更新对应每个节点的最小花费
380 if(!visited[v] &&
381 w < lowCost[v])
382 {
383 lowCost[v] = w;
384 }
385
386 cur = cur->next;
387 }
388 }
389
390 return total;
391}
392
3936. 代码怎么理解
394
395初始化:
396
397lowCost[start] = 0;
398
399意思:
400
401从 start 开始建树,
402把 start 加入树不需要花钱。
403
404每轮:
405
406int u = -1;
407
408for(int i = 0; i < n; i++)
409{
410 if(!visited[i] &&
411 (u == -1 || lowCost[i] < lowCost[u]))
412 {
413 u = i;
414 }
415}
416
417意思:找当前最便宜能接入生成树的点。
418
419加入总代价:
420
421total += lowCost[u];
422
423意思:
424
425把 u 接入生成树需要花 lowCost[u]。
426
427更新邻居:
428
429if(!visited[v] && w < lowCost[v])
430{
431 lowCost[v] = w;
432}
433
434意思:
435
436如果从 u 接到 v 更便宜,
437就更新 v 的接入成本。
438
4397. Prim 例子
440
441图:
442
4430 --2-- 1
444| |
4455 1
446| |
4472-------
448
449边:
450
4510-1 = 2
4520-2 = 5
4531-2 = 1
454
455从 0 开始。
456
457初始:
458
459lowCost[0]=0
460lowCost[1]=INF
461lowCost[2]=INF
462
463加入 0:
464
465更新:
466lowCost[1]=2
467lowCost[2]=5
468
469选最小:
470
4711,花费2
472
473加入 1:
474
475通过边 1-2,权值1
476lowCost[2]=min(5,1)=1
477
478选:
479
4802,花费1
481
482总代价:0 + 2 + 1 = 3
483
484最小生成树边权和:3
485
486四、Dijkstra 和 Prim 对比
487
4881. 代码结构像
489
490它们都:
491
4921. 初始化数组
4932. 每轮找一个当前最小的点
4943. visited[u] = 1
4954. 用 u 更新邻居
4962. 但是含义不同
497
498Dijkstra:
499
500dist[i] 表示 start 到 i 的最短路径长度
501
502Prim:
503
504lowCost[i] 表示当前生成树接到 i 的最小边权
5053. 更新公式不同
506
507Dijkstra:
508
509dist[v] = dist[u] + w;
510
511Prim:
512
513lowCost[v] = w;
514
515五、完整使用例子
516int main()
517{
518 int n = 3;
519
520 initGraph(n);
521
522 addUndirectedEdge(0, 1, 2);
523 addUndirectedEdge(0, 2, 5);
524 addUndirectedEdge(1, 2, 1);
525
526 int dist[MAXN];
527
528 dijkstra(n, 0, dist);
529
530 printf("Dijkstra:\n");
531
532 for(int i = 0; i < n; i++)
533 {
534 printf("0 -> %d = %d\n", i, dist[i]);
535 }
536
537 int mst = prim(n, 0);
538
539 printf("Prim MST = %d\n", mst);
540
541 return 0;
542}
543
544输出应该是:
545
546Dijkstra:
5470 -> 0 = 0
5480 -> 1 = 2
5490 -> 2 = 3
550
551Prim MST = 3
552
553六、总结
554Dijkstra
555
556问题:
557 从一个起点到所有点的最短路
558
559条件:
560 边权非负
561
562数组:
563 dist[i] = start 到 i 的当前最短距离
564
565每轮:
566 找未确定点中 dist 最小的点 u
567
568更新:
569 dist[v] = min(dist[v], dist[u] + w)
570
571--------------------------------
572
573最小生成树 MST
574
575问题:
576 把所有点连起来,并且总代价最小
577
578条件:
579 无向连通带权图
580
581特点:
582 n 个点
583 n-1 条边
584 无环
585 连通
586
587--------------------------------
588
589Prim
590
591问题:
592 求最小生成树
593
594数组:
595 lowCost[i] = 当前生成树接到 i 的最小边权
596
597每轮:
598 找未加入生成树中 lowCost 最小的点 u
599
600更新:
601 lowCost[v] = min(lowCost[v], w)
602
603--------------------------------
604
605最大区别:
606
607Dijkstra 看路径总长度:
608
609 dist[u] + w
610
611Prim 看接入生成树的单条边:
612
613 w
614
615Dijkstra 是“从起点走到各点最短”;
616Prim 是“把所有点接进来总代价最小”。
一、Dijkstra 单源最短路
- 前提条件
Dijkstra 用来求:从一个起点 start 到所有点的最短距离
要求:边权不能为负数
适合:
带权图
无负权边
单源最短路
- 核心概念
维护两个数组:dist[i]
表示:从 start 到 i 的当前最短距离
visited[i]
表示:i 这个点的最短路是否已经确定
每一轮做两件事:
- 从所有未确定的点里,找 dist 最小的点 u
- 用 u 去更新它的邻接点 v
更新公式:
if(dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
}
这一步叫:松弛 relax
- 图结构:邻接表
#include <stdio.h>
#include <stdlib.h>
#define MAXN 1000
#define INF 1000000000
struct EdgeNode {
int to;
int weight;
struct EdgeNode* next;
};
struct EdgeNode* adj[MAXN];
初始化:
void initGraph(int n)
{
for(int i = 0; i < n; i++)
{
adj[i] = NULL;
}
}
加有向边:
void addDirectedEdge(int u, int v, int w)
{
struct EdgeNode* node =
malloc(sizeof(struct EdgeNode));
node->to = v;
node->weight = w;
node->next = adj[u];
adj[u] = node;
}
加无向边:
void addUndirectedEdge(int u, int v, int w)
{
addDirectedEdge(u, v, w);
addDirectedEdge(v, u, w);
}
-
Dijkstra
void dijkstra(int n, int start, int dist[])
{
int visited[MAXN];
for(int i = 0; i < n; i++)
{
dist[i] = INF;
visited[i] = 0;
}
dist[start] = 0;
//这里先全部初始化,把最短路径先都设置为无穷,然后全部未遍历都是0,起始位置设置为0,起始到自己距离为0
for(int step = 0; step < n; step++)
{
int u = -1;
for(int i = 0; i < n; i++)
{
if(!visited[i] &&
(u == -1 || dist[i] < dist[u]))
{
u = i;
}
}
//这里第一次就是找到start对应的位置之后停止,之后的循环需要找每次相邻的dist最小的值开始确定最小的i给到u,
//step 控制轮数
//u 每一轮清空给-1
//内层 for 每一轮重新找当前 dist 最小且没 visited 的点。也就是遇到visited遍历过的跳过到后面,找未遍历的
if(u == -1 || dist[u] == INF)
{
break;
}
//这里是如果找不到相邻的了,或者直接断开了,就停止循环了
visited[u] = 1;
struct EdgeNode* cur = adj[u];
while(cur != NULL)
{
int v = cur->to;
int w = cur->weight;
if(!visited[v] &&
dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
}
//这里就是找到相邻的最短的路径更新到dist里面
cur = cur->next;
}
}
}
- 代码怎么理解
这段:
int u = -1;
for(int i = 0; i < n; i++)
{
if(!visited[i] &&
(u == -1 || dist[i] < dist[u]))
{
u = i;
}
}
意思是:
从所有还没确定最短路的点中,
找 dist 最小的点。
这段:
if(u == -1 || dist[u] == INF)
{
break;
}
意思是:
剩下的点已经到不了了,
直接结束。
这段:
if(dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
}
意思是:
如果 start -> u -> v
比原来的 start -> v 更短,
就更新 dist[v]。
- 例子
图:
0 --2-- 1
| |
5 1
| |
2-------
边:
0-1 权值2
0-2 权值5
1-2 权值1
从 0 开始。
初始:
dist[0]=0
dist[1]=INF
dist[2]=INF
选 0:
更新 1:dist[1]=2
更新 2:dist[2]=5
现在:
dist = [0,2,5]
选 1:
通过 1 到 2:
dist[2] = min(5, 2+1) = 3
现在:dist = [0,2,3]
选 2,结束。
答案:
0 到 0 = 0
0 到 1 = 2
0 到 2 = 3
- Dijkstra 总结
Dijkstra
解决:
单源最短路
条件:
边权 >= 0
核心:
每次选 dist 最小且未确定的点
更新:
dist[v] = min(dist[v], dist[u] + w)
二、最小生成树 MST
- 前提条件
最小生成树只针对:无向连通带权图
目标:
选出一些边,把所有点连起来,并且总权值最小
要求:
- 所有点连通
- 没有环
- 边数是 n-1
- 总边权最小
- 生成树是什么
如果有 n 个点,那么生成树一定有:
n - 1 条边
例如 4 个点,生成树有 3 条边。
少于 n-1 条边,可能连不起来
多于 n-1 条边,一定有环
- 最小生成树和最短路区别
最短路:关心从一个点到另一个点的路径最短
最小生成树:关心把所有点连起来总代价最小
它们不是一个问题。
三、Prim 算法
- 前提条件
Prim 用来求:最小生成树
适合:无向连通带权图
- 核心概念
Prim 的思想:
从一个点开始,逐渐扩大生成树。
每次选择一条最便宜的边,把一个新点加入生成树。
维护两个数组:lowCost[i]
表示:当前生成树连接到 i 点的最小边权
visited[i]
表示:i 是否已经加入生成树
- 和 Dijkstra 最像的地方
Prim 每轮也是:
从未加入的点中,找 lowCost 最小的点 u。
然后加入生成树:
visited[u] = 1;
- 和 Dijkstra 最大区别
Dijkstra 更新:
dist[v] = dist[u] + w;
意思:
从 start 到 v 的路径长度
Prim 更新:
lowCost[v] = w;
意思:
生成树连到 v 的最小边权
Prim 不关心路径总长度,只关心:
把一个新点接进树,用哪条边最便宜
-
Prim 代码
int prim(int n, int start)
{
int lowCost[MAXN];
int visited[MAXN];
for(int i = 0; i < n; i++)
{
lowCost[i] = INF;
visited[i] = 0;
}
lowCost[start] = 0;
//还是初始化之后把start对应的花费设为0
int total = 0;
for(int step = 0; step < n; step++)
{
int u = -1;
for(int i = 0; i < n; i++)
{
if(!visited[i] &&
(u == -1 || lowCost[i] < lowCost[u]))
{
u = i;
}
}
//这里还是每一轮找花费最小的元素
if(u == -1 || lowCost[u] == INF)
{
return -1;
}
visited[u] = 1;
//被认定遍历过之后就加入生成树
total += lowCost[u];
struct EdgeNode* cur = adj[u];
while(cur != NULL)
{
int v = cur->to;
int w = cur->weight;
//这里不是对应要求最短路径,只更新对应每个节点的最小花费
if(!visited[v] &&
w < lowCost[v])
{
lowCost[v] = w;
}
cur = cur->next;
}
}
return total;
}
- 代码怎么理解
初始化:
lowCost[start] = 0;
意思:
从 start 开始建树,
把 start 加入树不需要花钱。
每轮:
int u = -1;
for(int i = 0; i < n; i++)
{
if(!visited[i] &&
(u == -1 || lowCost[i] < lowCost[u]))
{
u = i;
}
}
意思:找当前最便宜能接入生成树的点。
加入总代价:
total += lowCost[u];
意思:
把 u 接入生成树需要花 lowCost[u]。
更新邻居:
if(!visited[v] && w < lowCost[v])
{
lowCost[v] = w;
}
意思:
如果从 u 接到 v 更便宜,
就更新 v 的接入成本。
- Prim 例子
图:
0 --2-- 1
| |
5 1
| |
2-------
边:
0-1 = 2
0-2 = 5
1-2 = 1
从 0 开始。
初始:
lowCost[0]=0
lowCost[1]=INF
lowCost[2]=INF
加入 0:
更新:
lowCost[1]=2
lowCost[2]=5
选最小:
1,花费2
加入 1:
通过边 1-2,权值1
lowCost[2]=min(5,1)=1
选:
2,花费1
总代价:0 + 2 + 1 = 3
最小生成树边权和:3
四、Dijkstra 和 Prim 对比
- 代码结构像
它们都:
- 初始化数组
- 每轮找一个当前最小的点
- visited[u] = 1
- 用 u 更新邻居
- 但是含义不同
Dijkstra:
dist[i] 表示 start 到 i 的最短路径长度
Prim:
lowCost[i] 表示当前生成树接到 i 的最小边权
3. 更新公式不同
Dijkstra:
dist[v] = dist[u] + w;
Prim:
lowCost[v] = w;
五、完整使用例子
int main()
{
int n = 3;
initGraph(n);
addUndirectedEdge(0, 1, 2);
addUndirectedEdge(0, 2, 5);
addUndirectedEdge(1, 2, 1);
int dist[MAXN];
dijkstra(n, 0, dist);
printf("Dijkstra:\n");
for(int i = 0; i < n; i++)
{
printf("0 -> %d = %d\n", i, dist[i]);
}
int mst = prim(n, 0);
printf("Prim MST = %d\n", mst);
return 0;
}
输出应该是:
Dijkstra:
0 -> 0 = 0
0 -> 1 = 2
0 -> 2 = 3
Prim MST = 3
六、总结
Dijkstra
问题:
从一个起点到所有点的最短路
条件:
边权非负
数组:
dist[i] = start 到 i 的当前最短距离
每轮:
找未确定点中 dist 最小的点 u
更新:
dist[v] = min(dist[v], dist[u] + w)
最小生成树 MST
问题:
把所有点连起来,并且总代价最小
条件:
无向连通带权图
特点:
n 个点
n-1 条边
无环
连通
Prim
问题:
求最小生成树
数组:
lowCost[i] = 当前生成树接到 i 的最小边权
每轮:
找未加入生成树中 lowCost 最小的点 u
更新:
lowCost[v] = min(lowCost[v], w)
最大区别:
Dijkstra 看路径总长度:
dist[u] + w
Prim 看接入生成树的单条边:
w
Dijkstra 是“从起点走到各点最短”;
Prim 是“把所有点接进来总代价最小”。