返回 LeetCode 刷题

Markdown File

拓扑排序和无权最短路径

拓扑排序和无权最短路径.md

1一、拓扑排序 Topological Sort
21. 前提条件
3
4拓扑排序只用于:
5
6有向无环图 DAG
7
8也就是:有方向,不能有环
9
10典型场景:
11
12课程先修关系
13任务依赖关系
14编译依赖
15
16例如:
17
180 -> 1
190 -> 2
201 -> 3
212 -> 3
22
23表示:
24
25必须先做 0
26才能做 1 和 2
27最后才能做 3
28
292. 核心概念:入度
30
31入度:有多少条边指向这个点
32
33如果一个点入度为 0:
34
35说明没有前置条件
36可以先处理
37
38拓扑排序核心:
39
401. 把所有入度为 0 的点入队
412. 每次弹出一个点
423. 删除它连出去的边
434. 被影响的点入度减 1
445. 如果某个点入度变成 0,入队
453. 例子流程
46
47图:
48
490 -> 1
500 -> 2
511 -> 3
522 -> 3
53
54入度:
55
560: 0
571: 1
582: 1
593: 2
60
61先入队:0
62
63弹出 0:
64
65结果:[0]
66
67删除 0->1,0->2:
68
691 入度变 0
702 入度变 0
71
72入队:1, 2
73
74弹出 1:结果:[0,1]
75
76删除 1->3: 3 入度从 2 变 1
77
78弹出 2: 结果:[0,1,2]
79
80删除 2->3: 3 入度从 1 变 0
81
82入队 3。
83
84最后:[0,1,2,3]
85
86这就是一种拓扑序。
87
884. C 代码模板:邻接表 + 队列
89
90#include <stdlib.h>
91
92#define MAXN 1000
93
94struct EdgeNode {
95 int to;
96 struct EdgeNode* next;
97};
98
99struct EdgeNode* adj[MAXN];
100int indegree[MAXN];
101//indegree就是表示入度的那个数组
102void initGraph(int n)
103{
104 for(int i = 0; i < n; i++)
105 {
106 adj[i] = NULL;
107 indegree[i] = 0;
108 }
109}
110
111void addEdge(int u, int v)
112{
113 struct EdgeNode* node =
114 malloc(sizeof(struct EdgeNode));
115
116 node->to = v;
117 node->next = adj[u];
118 adj[u] = node;
119
120 indegree[v]++;
121}
122
123拓扑排序:
124
125int topologicalSort(int n, int result[])
126{
127 int queue[MAXN];
128 int front = 0;
129 int rear = 0;
130//入度为0的先入队
131 for(int i = 0; i < n; i++)
132 {
133 if(indegree[i] == 0)
134 {
135 queue[rear++] = i;
136 }
137 }
138
139 int count = 0;
140
141 while(front < rear)
142 {
143 int u = queue[front++];
144
145 result[count++] = u;
146
147 struct EdgeNode* cur = adj[u];
148 //选中入队元素之后出队
149 while(cur != NULL)
150 {
151 int v = cur->to;
152
153 indegree[v]--;
154 //出队之后对应连接的边入度减-1,如果为0那就继续入队,不为0就继续遍历邻接表的下一个元素,如果有入度为0就入队,直到遍历完这个元素的邻接表之后到下一个元素
155 if(indegree[v] == 0)
156 {
157 queue[rear++] = v;
158 }
159
160 cur = cur->next;
161 }
162 }
163
164 return count == n;
165}
166
167返回值:
168
1691:拓扑排序成功,没有环
1700:失败,说明有环
171
1725. 为什么能判断有环?
173
174如果有环:0 -> 1 -> 2 -> 0
175
176每个点入度都不是 0。
177
178队列一开始可能为空。
179
180没有点能被处理。
181
182最后:count < n
183
184说明还有点没处理。
185
186所以:图里有环
187
1886.拓扑排序
189
190前提:
191 有向无环图 DAG
192
193核心:
194 入度
195
196步骤:
197 1. 统计每个点入度
198 2. 入度为 0 的点入队
199 3. 出队一个点,加入结果
200 4. 删除它的出边
201 5. 邻接点入度减 1
202 6. 新的入度为 0 的点入队
203
204判断有环:
205 如果最终处理点数 < n
206 说明有环
207
208复杂度:
209 O(n + m)
210
211二、无权最短路径 BFS
2121. 前提条件
213
214无权图:每条边的代价都一样
215
216比如:走一条边,距离 +1
217
218这种情况下,最短路用:BFS
219
220不用 Dijkstra。
221
2222. 核心概念
223
224BFS 是一层一层扩展。
225
226从起点 s 出发:
227
228第 0 层:s
229第 1 层:s 一步能到的点
230第 2 层:两步能到的点
231第 3 层:三步能到的点
232
233第一次到达某个点时,就是最短距离。
234
2353. 例子
236
237图:
238
2390 - 1 - 3
240| |
2412 - 4
242
243从 0 出发。
244
245初始:dist[0] = 0
246
2470 的邻居:1, 2
248
249所以:
250
251dist[1] = 1
252dist[2] = 1
253
254再从 1 出发:3, 4
255
256所以:
257
258dist[3] = 2
259dist[4] = 2
260
261结果:0 到 3 的最短距离 = 2
262
2634. BFS 代码模板:邻接表
264#include <stdlib.h>
265
266#define MAXN 1000
267
268struct EdgeNode {
269 int to;
270 struct EdgeNode* next;
271};
272
273struct EdgeNode* adj[MAXN];
274
275void initGraph(int n)
276{
277 for(int i = 0; i < n; i++)
278 {
279 adj[i] = NULL;
280 }
281}
282
283void addUndirectedEdge(int u, int v)
284{
285 struct EdgeNode* node1 =
286 malloc(sizeof(struct EdgeNode));
287
288 node1->to = v;
289 node1->next = adj[u];
290 adj[u] = node1;
291
292 struct EdgeNode* node2 =
293 malloc(sizeof(struct EdgeNode));
294
295 node2->to = u;
296 node2->next = adj[v];
297 adj[v] = node2;
298}
299//因为是无权路径,所以需要malloc两个node之后互相建立路径
300
301BFS 最短路:
302
303void bfsShortestPath(int n, int start, int dist[])
304{
305 int queue[MAXN];
306 int front = 0;
307 int rear = 0;
308
309 for(int i = 0; i < n; i++)
310 {
311 dist[i] = -1;
312 }
313//先初始化都为-1
314 dist[start] = 0;
315 queue[rear++] = start;
316
317 while(front < rear)
318 {
319 int u = queue[front++];
320
321 struct EdgeNode* cur = adj[u];
322
323 while(cur != NULL)
324 {
325 int v = cur->to;
326
327//从start标定的开始先入队,之后看邻接表所指向的边,然后改变其dist之后再入队,当然首先还要检查完start本身其他相邻的边,之后再一次查看第二次所指向的其他相邻边
328//比如说dist[0]=-1,之后定义为0,之后给了dist[1]变成1,之后dist[3]原本是-1,但是变成了1+1=2,但是dist[0]已经变了,所以有环也不会检查,最后return出来dist所需数的值就是要的答案
329 if(dist[v] == -1)
330 {
331 dist[v] = dist[u] + 1;
332 queue[rear++] = v;
333 }
334
335 cur = cur->next;
336 }
337 }
338}
3395. 为什么 dist[v] == -1 才更新?
340
341dist[v] == -1 表示:v 还没访问过
342
343BFS 第一次访问到 v 时,一定是最短距离。
344
345所以:dist[v] = dist[u] + 1;
346
347之后再遇到 v,不用更新。
348
3496. 无权最短路笔记
350无权最短路
351
352前提:
353 每条边权值相同
354
355算法:
356 BFS
357
358核心:
359 一层一层扩展
360
361dist[start] = 0
362
363如果从 u 到 v:
364 dist[v] = dist[u] + 1
365
366访问过的点不再更新
367
368原因:
369 BFS 第一次到达某点
370 就是最短路径
371
372复杂度:
373 O(n + m)
374
375三、拓扑排序 vs BFS 最短路
376拓扑排序:
377
378用队列
379处理入度为 0 的点
380解决有向依赖问题
381
382--------------------------------
383
384BFS最短路:
385
386用队列
387一层一层扩展
388解决无权最短距离问题
389
390一句话:
391
392拓扑排序的队列装“当前没有前置条件的点”;
393BFS 的队列装“当前这一层能扩展出去的点”。
Rendered Preview

一、拓扑排序 Topological Sort

  1. 前提条件

拓扑排序只用于:

有向无环图 DAG

也就是:有方向,不能有环

典型场景:

课程先修关系
任务依赖关系
编译依赖

例如:

0 -> 1
0 -> 2
1 -> 3
2 -> 3

表示:

必须先做 0
才能做 1 和 2
最后才能做 3

  1. 核心概念:入度

入度:有多少条边指向这个点

如果一个点入度为 0:

说明没有前置条件
可以先处理

拓扑排序核心:

  1. 把所有入度为 0 的点入队
  2. 每次弹出一个点
  3. 删除它连出去的边
  4. 被影响的点入度减 1
  5. 如果某个点入度变成 0,入队
  6. 例子流程

图:

0 -> 1
0 -> 2
1 -> 3
2 -> 3

入度:

0: 0
1: 1
2: 1
3: 2

先入队:0

弹出 0:

结果:[0]

删除 0->1,0->2:

1 入度变 0
2 入度变 0

入队:1, 2

弹出 1:结果:[0,1]

删除 1->3: 3 入度从 2 变 1

弹出 2: 结果:[0,1,2]

删除 2->3: 3 入度从 1 变 0

入队 3。

最后:[0,1,2,3]

这就是一种拓扑序。

  1. C 代码模板:邻接表 + 队列

#include <stdlib.h>

#define MAXN 1000

struct EdgeNode {
int to;
struct EdgeNode* next;
};

struct EdgeNode* adj[MAXN];
int indegree[MAXN];
//indegree就是表示入度的那个数组
void initGraph(int n)
{
for(int i = 0; i < n; i++)
{
adj[i] = NULL;
indegree[i] = 0;
}
}

void addEdge(int u, int v)
{
struct EdgeNode* node =
malloc(sizeof(struct EdgeNode));

node->to = v;
node->next = adj[u];
adj[u] = node;

indegree[v]++;

}

拓扑排序:

int topologicalSort(int n, int result[])
{
int queue[MAXN];
int front = 0;
int rear = 0;
//入度为0的先入队
for(int i = 0; i < n; i++)
{
if(indegree[i] == 0)
{
queue[rear++] = i;
}
}

int count = 0;

while(front < rear)
{
    int u = queue[front++];

    result[count++] = u;

    struct EdgeNode* cur = adj[u];
//选中入队元素之后出队
    while(cur != NULL)
    {
        int v = cur->to;

        indegree[v]--;
//出队之后对应连接的边入度减-1,如果为0那就继续入队,不为0就继续遍历邻接表的下一个元素,如果有入度为0就入队,直到遍历完这个元素的邻接表之后到下一个元素
        if(indegree[v] == 0)
        {
            queue[rear++] = v;
        }

        cur = cur->next;
    }
}

return count == n;

}

返回值:

1:拓扑排序成功,没有环
0:失败,说明有环

  1. 为什么能判断有环?

如果有环:0 -> 1 -> 2 -> 0

每个点入度都不是 0。

队列一开始可能为空。

没有点能被处理。

最后:count < n

说明还有点没处理。

所以:图里有环

6.拓扑排序

前提:
有向无环图 DAG

核心:
入度

步骤:
1. 统计每个点入度
2. 入度为 0 的点入队
3. 出队一个点,加入结果
4. 删除它的出边
5. 邻接点入度减 1
6. 新的入度为 0 的点入队

判断有环:
如果最终处理点数 < n
说明有环

复杂度:
O(n + m)

二、无权最短路径 BFS

  1. 前提条件

无权图:每条边的代价都一样

比如:走一条边,距离 +1

这种情况下,最短路用:BFS

不用 Dijkstra。

  1. 核心概念

BFS 是一层一层扩展。

从起点 s 出发:

第 0 层:s
第 1 层:s 一步能到的点
第 2 层:两步能到的点
第 3 层:三步能到的点

第一次到达某个点时,就是最短距离。

  1. 例子

图:

0 - 1 - 3
| |
2 - 4

从 0 出发。

初始:dist[0] = 0

0 的邻居:1, 2

所以:

dist[1] = 1
dist[2] = 1

再从 1 出发:3, 4

所以:

dist[3] = 2
dist[4] = 2

结果:0 到 3 的最短距离 = 2

  1. BFS 代码模板:邻接表
    #include <stdlib.h>

#define MAXN 1000

struct EdgeNode {
int to;
struct EdgeNode* next;
};

struct EdgeNode* adj[MAXN];

void initGraph(int n)
{
for(int i = 0; i < n; i++)
{
adj[i] = NULL;
}
}

void addUndirectedEdge(int u, int v)
{
struct EdgeNode* node1 =
malloc(sizeof(struct EdgeNode));

node1->to = v;
node1->next = adj[u];
adj[u] = node1;

struct EdgeNode* node2 =
    malloc(sizeof(struct EdgeNode));

node2->to = u;
node2->next = adj[v];
adj[v] = node2;

}
//因为是无权路径,所以需要malloc两个node之后互相建立路径

BFS 最短路:

void bfsShortestPath(int n, int start, int dist[])
{
int queue[MAXN];
int front = 0;
int rear = 0;

for(int i = 0; i < n; i++)
{
    dist[i] = -1;
}

//先初始化都为-1
dist[start] = 0;
queue[rear++] = start;

while(front < rear)
{
    int u = queue[front++];

    struct EdgeNode* cur = adj[u];

    while(cur != NULL)
    {
        int v = cur->to;

//从start标定的开始先入队,之后看邻接表所指向的边,然后改变其dist之后再入队,当然首先还要检查完start本身其他相邻的边,之后再一次查看第二次所指向的其他相邻边
//比如说dist[0]=-1,之后定义为0,之后给了dist[1]变成1,之后dist[3]原本是-1,但是变成了1+1=2,但是dist[0]已经变了,所以有环也不会检查,最后return出来dist所需数的值就是要的答案
if(dist[v] == -1)
{
dist[v] = dist[u] + 1;
queue[rear++] = v;
}

        cur = cur->next;
    }
}

}
5. 为什么 dist[v] == -1 才更新?

dist[v] == -1 表示:v 还没访问过

BFS 第一次访问到 v 时,一定是最短距离。

所以:dist[v] = dist[u] + 1;

之后再遇到 v,不用更新。

  1. 无权最短路笔记
    无权最短路

前提:
每条边权值相同

算法:
BFS

核心:
一层一层扩展

dist[start] = 0

如果从 u 到 v:
dist[v] = dist[u] + 1

访问过的点不再更新

原因:
BFS 第一次到达某点
就是最短路径

复杂度:
O(n + m)

三、拓扑排序 vs BFS 最短路
拓扑排序:

用队列
处理入度为 0 的点
解决有向依赖问题


BFS最短路:

用队列
一层一层扩展
解决无权最短距离问题

一句话:

拓扑排序的队列装“当前没有前置条件的点”;
BFS 的队列装“当前这一层能扩展出去的点”。