1第一章 Kruskal 最小生成树
21. 前提条件
3使用条件
4
5Kruskal 用来求:
6
7最小生成树(MST)
8
9要求:无向连通带权图
10
11和 Prim 一样。
12
13和 Prim 的区别
14
15Prim:长点
16
17从一个点开始,不断加入新的点
18
19Kruskal:挑边
20
21每次挑最短的边,直到所有点连通
22
232. 核心概念
24
25例如:
26
27A----1----B
28
29A----4----C
30
31B----2----C
32
33所有边:
34
35AB 1
36
37BC 2
38
39AC 4
40
41Kruskal:
42
43第一步:
44排序:
45AB 1
46BC 2
47AC 4
48
49然后:依次加入。
50
51第一条:AB加入。
52
53第二条:BC加入。
54
55第三条:AC如果加入:
56
57 A
58
59/ \
60
61B-C
62
63会形成环
64
65所以:不能加入->结束
66
67核心思想
68
69按边权从小到大排序
70能加就加
71形成环就跳过
72
733. 为什么要并查集?
74
75问题:如何知道:
76
77加入一条边
78
79会不会形成环?
80
81例如:已经有:
82
83A-B
84
85B-C
86
87现在:A-C加入。
88
89其实:A已经能到C,所以:加入以后形成环
90
91用:并查集(Union Find)判断:两个点是不是已经连通。
92
934. 并查集
94
95维护:parent[]
96
97例如:
98
990
100
1011
102
1032
104
105开始:
106
107parent
108
1090
110
1111
112
1132
114
115说明:自己是一棵树
116
117加入0-1以后:
118
119parent
120
1210
122
1230
124
1252
126
127表示:
128
1290
130
131|
132
1331
134
135再加入:
136
1371-2
138
139得到:
140
141parent
142
1430
144
1450
146
1470
148
149表示:
150
1510
152
153|
154
1551
156
157|
158
1592
160
161三个已经连通。
162
1635. 并查集代码
164
165初始化:
166
167void init(int n)
168{
169 for(int i=0;i<n;i++)
170 {
171 parent[i]=i;
172 }
173}
174
175查找根:
176
177int find(int x)
178{
179 if(parent[x]!=x)
180 {
181 parent[x]=find(parent[x]);
182 }
183
184 return parent[x];
185}
186//查找如果不等于自己就返回他的根节点
187
188这是:路径压缩
189
190合并:
191
192void unite(int x,int y)
193{
194 int fx=find(x);
195 int fy=find(y);
196
197 if(fx!=fy)
198 {
199 parent[fx]=fy;
200 }
201}
202//把两个树合并到一块
203
2046. 边结构
205struct Edge
206{
207 int u;
208 int v;
209 int w;
210};
211
212表示:
213
214u
215
216↓
217
218边
219
220↓
221
222v
223
224权值w
225
2267. Kruskal代码
227int cmp(const void* a,const void* b)
228{
229 return ((struct Edge*)a)->w
230 - ((struct Edge*)b)->w;
231}
232//先写比较权重的函数
233int kruskal(struct Edge edge[],
234 int n,
235 int m)
236{
237 init(n);
238//初始化创建树
239 qsort(edge,
240 m,
241 sizeof(struct Edge),
242 cmp);
243//之后qsort自动排序,排边结构,m个元素,边结构,按照cmp的顺序
244 int total=0;
245 int cnt=0;
246
247 for(int i=0;i<m;i++)
248 {
249 int u=edge[i].u;
250 int v=edge[i].v;
251//这里就是查找到边结构,如果说两个对应不是一个根,那就把他们合起来,之后total最小生成树增加
252 if(find(u)!=find(v))
253 {
254 unite(u,v);
255
256 total+=edge[i].w;
257
258 cnt++;
259 }
260 }
261//不等于n-1说明有断开了,没有最小生成树
262 if(cnt!=n-1)
263 {
264 return -1;
265 }
266
267 return total;
268}
269
270总结就是:
271把所有边按长度排队。从最短开始。
272
273如果不会形成环:就拿。
274
275如果会形成环:就丢掉。
276
277直到拿够n-1条边。
278第一章 深度优先搜索(DFS)
2791. 前提条件
280
281DFS(Depth First Search)是一种图和树的遍历算法。
282
283主要解决:
284
285① 图遍历
286
287② 树遍历
288
289③ 连通块
290
291④ 判断是否连通
292
293⑤ 回溯搜索
294
295⑥ 岛屿问题
296
297⑦ 路径搜索
298
299适用于:树、图(有向图、无向图)、迷宫
300
3012. 核心思想
302
303一条路走到底
304走不通再回来
305
306例如:
307
308 0
309 / \
310 1 2
311 /
312 3
313
314DFS访问顺序:
315
3160
317
318↓
319
3201
321
322↓
323
3243
325
326↑
327
328↓
329
3302
331
332
333一直往下面钻
334
335钻不动返回上一层
336
337继续其它分支
338
339这就是:回溯(Backtracking)
340
3413. DFS为什么要递归?
342
343例如:dfs(0)
344
345发现:
346
3470
348
349↓
350
3511
352
353于是:dfs(1)
354
355继续:
356
3571
358
359↓
360
3613
362
363于是:dfs(3)
364
3653没有儿子。
366
367于是:dfs(3)结束
368
369自动:返回dfs(1)
370
371继续:dfs(1)结束
372
373返回:dfs(0)
374
375继续:2
376
377整个过程:
378
379dfs(0)
380
381↓
382
383dfs(1)
384
385↓
386
387dfs(3)
388
389↑
390
391dfs(1)
392
393↑
394
395dfs(0)
396
397↓
398
399dfs(2)
400
4014. DFS递归树
402
403例如:
404
405 0
406 / \
407 1 2
408 /
409 3
410
411真正调用过程:
412
413dfs(0)
414
415│
416
417├────dfs(1)
418
419│ │
420
421│ └────dfs(3)
422
423│
424
425└────dfs(2)
426
427特别容易理解。
428
4295. 图的DFS代码
430
431图采用邻接表:
432
433struct EdgeNode
434{
435 int to;
436 struct EdgeNode* next;
437};
438
439struct EdgeNode* adj[MAXN];
440
441DFS:
442
443int visited[MAXN];
444
445void dfs(int u)
446{
447 visited[u] = 1;
448
449 printf("%d ", u);
450
451 struct EdgeNode* cur = adj[u];
452
453 while(cur != NULL)
454 {
455 int v = cur->to;
456
457 if(!visited[v])
458 {
459 dfs(v);
460 }
461
462 cur = cur->next;
463 }
464}
465
4667. 每一句代码解释
467
468第一句:
469
470visited[u] = 1;
471
472表示:已经访问过u以后不能再访问
473
474第二句:printf("%d ",u);
475
476访问当前节点。
477
478第三句:
479
480struct EdgeNode* cur = adj[u];
481
482表示:找到u所有邻居
483
484例如:
485
4860
487
488↓
489
4901
491
492↓
493
4942
495
496此时:
497
498cur
499
500↓
501
5021
503
504↓
505
5062
507
508↓
509
510NULL
511
512第四句:while(cur!=NULL)
513
514表示:依次遍历所有邻居
515
516第五句:
517
518int v = cur->to;
519
520得到:当前邻居是谁
521
522例如:
523
524u=0
525
526↓
527
5281
529
530得到:v=1
531
532第六句:
533
534if(!visited[v])
535
536表示:没访问过
537
538继续DFS
539
540否则:
541
542跳过
543
544避免死循环。
545
546最后:
547
548cur = cur->next;
549
550表示:
551
552继续看下一个邻居
553
5548. 完整例子
555
556图:
557
558 0
559 / \
560 1 2
561 /
562 3
563
564邻接表:
565
566adj[0]
567
568↓
569
5701
571
572↓
573
5742
575
576↓
577
578NULL
579
580adj[1]
581
582↓
583
5843
585
586↓
587
588NULL
589
590开始:
591
592dfs(0)
593
594第一次:
595
596visited
597
5981
599
6000
601
6020
603
6040
605
606打印:
607
6080
609
610进入:
611
612dfs(1)
613
614第二次:
615
616visited
617
6181
619
6201
621
6220
623
6240
625
626打印:
627
6280 1
629
630进入:
631
632dfs(3)
633
634第三次:
635
636visited
637
6381
639
6401
641
6420
643
6441
645
646打印:0 1 3
647
648没有邻居。
649
650返回。
651
652回到:dfs(1)结束。
653
654返回:dfs(0)继续:2
655
656进入:
657
658dfs(2)
659
660打印:
661
6620 1 3 2
663
664结束。
665
666最终:
667
668访问顺序
6690
670
671↓
672
6731
674
675↓
676
6773
678
679↓
680
6812
682
6839. 树的DFS
684
685树更简单。
686
687因为:没有环
688
689所以:不用:visited[]
690
691例如:
692
693void dfs(struct TreeNode* root)
694{
695 if(root == NULL)
696 {
697 return;
698 }
699
700 printf("%d ",root->val);
701
702 dfs(root->left);
703
704 dfs(root->right);
705}
706
707其实就是:前序遍历
708
709所以:树的DFS
710
711就是递归遍历。
71210. 图DFS和树DFS区别
713
714树:
715
716没有环
717
718不用visited
719
720图:
721
722可能有环
723
724必须visited
725
726
72711. DFS复杂度
728
729邻接表:
730
731每个点访问一次
732
733每条边访问一次
734
735时间:O(V+E)
736
737V:顶点
738
739E:边
740
741空间:递归栈:O(V)
742
74312. DFS固定模板
744int visited[MAXN];
745
746void dfs(int u)
747{
748 visited[u] = 1;
749
750 //处理当前点
751
752 struct EdgeNode* cur = adj[u];
753
754 while(cur != NULL)
755 {
756 int v = cur->to;
757
758 if(!visited[v])
759 {
760 dfs(v);
761 }
762
763 cur = cur->next;
764 }
765}
766
767 DFS总结
768DFS(Depth First Search)
769
770核心思想:
771
772 一条路走到底
773
774 走不通返回上一层
775
776数据结构:
777
778 系统递归栈(或显式栈)
779
780图:
781
782 必须visited[]
783
784树:
785
786 不需要visited
787
788时间复杂度:
789
790 O(V+E)
791
792模板:
793
794 visited[u]=1
795
796 遍历所有邻居
797
798 没访问继续dfs(v)
799
800应用:
801
802 图遍历
803 连通块
804 岛屿问题
805 回溯
806 树遍历
第一章 Kruskal 最小生成树
- 前提条件
使用条件
Kruskal 用来求:
最小生成树(MST)
要求:无向连通带权图
和 Prim 一样。
和 Prim 的区别
Prim:长点
从一个点开始,不断加入新的点
Kruskal:挑边
每次挑最短的边,直到所有点连通
- 核心概念
例如:
A----1----B
A----4----C
B----2----C
所有边:
AB 1
BC 2
AC 4
Kruskal:
第一步:
排序:
AB 1
BC 2
AC 4
然后:依次加入。
第一条:AB加入。
第二条:BC加入。
第三条:AC如果加入:
A
/ \
B-C
会形成环
所以:不能加入->结束
核心思想
按边权从小到大排序
能加就加
形成环就跳过
- 为什么要并查集?
问题:如何知道:
加入一条边
会不会形成环?
例如:已经有:
A-B
B-C
现在:A-C加入。
其实:A已经能到C,所以:加入以后形成环
用:并查集(Union Find)判断:两个点是不是已经连通。
- 并查集
维护:parent[]
例如:
0
1
2
开始:
parent
0
1
2
说明:自己是一棵树
加入0-1以后:
parent
0
0
2
表示:
0
|
1
再加入:
1-2
得到:
parent
0
0
0
表示:
0
|
1
|
2
三个已经连通。
- 并查集代码
初始化:
void init(int n)
{
for(int i=0;i<n;i++)
{
parent[i]=i;
}
}
查找根:
int find(int x)
{
if(parent[x]!=x)
{
parent[x]=find(parent[x]);
}
return parent[x];
}
//查找如果不等于自己就返回他的根节点
这是:路径压缩
合并:
void unite(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
{
parent[fx]=fy;
}
}
//把两个树合并到一块
- 边结构
struct Edge
{
int u;
int v;
int w;
};
表示:
u
↓
边
↓
v
权值w
-
Kruskal代码
int cmp(const void* a,const void* b)
{
return ((struct Edge*)a)->w
- ((struct Edge*)b)->w;
}
//先写比较权重的函数
int kruskal(struct Edge edge[],
int n,
int m)
{
init(n);
//初始化创建树
qsort(edge,
m,
sizeof(struct Edge),
cmp);
//之后qsort自动排序,排边结构,m个元素,边结构,按照cmp的顺序
int total=0;
int cnt=0;
for(int i=0;i<m;i++)
{
int u=edge[i].u;
int v=edge[i].v;
//这里就是查找到边结构,如果说两个对应不是一个根,那就把他们合起来,之后total最小生成树增加
if(find(u)!=find(v))
{
unite(u,v);
total+=edge[i].w;
cnt++;
}
}
//不等于n-1说明有断开了,没有最小生成树
if(cnt!=n-1)
{
return -1;
}
return total;
}
总结就是:
把所有边按长度排队。从最短开始。
如果不会形成环:就拿。
如果会形成环:就丢掉。
直到拿够n-1条边。
第一章 深度优先搜索(DFS)
- 前提条件
DFS(Depth First Search)是一种图和树的遍历算法。
主要解决:
① 图遍历
② 树遍历
③ 连通块
④ 判断是否连通
⑤ 回溯搜索
⑥ 岛屿问题
⑦ 路径搜索
适用于:树、图(有向图、无向图)、迷宫
- 核心思想
一条路走到底
走不通再回来
例如:
0
/ \
1 2
/
3
DFS访问顺序:
0
↓
1
↓
3
↑
↓
2
一直往下面钻
钻不动返回上一层
继续其它分支
这就是:回溯(Backtracking)
- DFS为什么要递归?
例如:dfs(0)
发现:
0
↓
1
于是:dfs(1)
继续:
1
↓
3
于是:dfs(3)
3没有儿子。
于是:dfs(3)结束
自动:返回dfs(1)
继续:dfs(1)结束
返回:dfs(0)
继续:2
整个过程:
dfs(0)
↓
dfs(1)
↓
dfs(3)
↑
dfs(1)
↑
dfs(0)
↓
dfs(2)
- DFS递归树
例如:
0
/ \
1 2
/
3
真正调用过程:
dfs(0)
│
├────dfs(1)
│ │
│ └────dfs(3)
│
└────dfs(2)
特别容易理解。
- 图的DFS代码
图采用邻接表:
struct EdgeNode
{
int to;
struct EdgeNode* next;
};
struct EdgeNode* adj[MAXN];
DFS:
int visited[MAXN];
void dfs(int u)
{
visited[u] = 1;
printf("%d ", u);
struct EdgeNode* cur = adj[u];
while(cur != NULL)
{
int v = cur->to;
if(!visited[v])
{
dfs(v);
}
cur = cur->next;
}
}
- 每一句代码解释
第一句:
visited[u] = 1;
表示:已经访问过u以后不能再访问
第二句:printf("%d ",u);
访问当前节点。
第三句:
struct EdgeNode* cur = adj[u];
表示:找到u所有邻居
例如:
0
↓
1
↓
2
此时:
cur
↓
1
↓
2
↓
NULL
第四句:while(cur!=NULL)
表示:依次遍历所有邻居
第五句:
int v = cur->to;
得到:当前邻居是谁
例如:
u=0
↓
1
得到:v=1
第六句:
if(!visited[v])
表示:没访问过
继续DFS
否则:
跳过
避免死循环。
最后:
cur = cur->next;
表示:
继续看下一个邻居
- 完整例子
图:
0
/ \
1 2
/
3
邻接表:
adj[0]
↓
1
↓
2
↓
NULL
adj[1]
↓
3
↓
NULL
开始:
dfs(0)
第一次:
visited
1
0
0
0
打印:
0
进入:
dfs(1)
第二次:
visited
1
1
0
0
打印:
0 1
进入:
dfs(3)
第三次:
visited
1
1
0
1
打印:0 1 3
没有邻居。
返回。
回到:dfs(1)结束。
返回:dfs(0)继续:2
进入:
dfs(2)
打印:
0 1 3 2
结束。
最终:
访问顺序
0
↓
1
↓
3
↓
2
- 树的DFS
树更简单。
因为:没有环
所以:不用:visited[]
例如:
void dfs(struct TreeNode* root)
{
if(root == NULL)
{
return;
}
printf("%d ",root->val);
dfs(root->left);
dfs(root->right);
}
其实就是:前序遍历
所以:树的DFS
就是递归遍历。
10. 图DFS和树DFS区别
树:
没有环
不用visited
图:
可能有环
必须visited
- DFS复杂度
邻接表:
每个点访问一次
每条边访问一次
时间:O(V+E)
V:顶点
E:边
空间:递归栈:O(V)
- DFS固定模板
int visited[MAXN];
void dfs(int u)
{
visited[u] = 1;
//处理当前点
struct EdgeNode* cur = adj[u];
while(cur != NULL)
{
int v = cur->to;
if(!visited[v])
{
dfs(v);
}
cur = cur->next;
}
}
DFS总结
DFS(Depth First Search)
核心思想:
一条路走到底
走不通返回上一层
数据结构:
系统递归栈(或显式栈)
图:
必须visited[]
树:
不需要visited
时间复杂度:
O(V+E)
模板:
visited[u]=1
遍历所有邻居
没访问继续dfs(v)
应用:
图遍历
连通块
岛屿问题
回溯
树遍历