返回 LeetCode 刷题

Markdown File

kruskal算法和深度优先搜索

kruskal算法和深度优先搜索.md

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 树遍历
Rendered Preview

第一章 Kruskal 最小生成树

  1. 前提条件
    使用条件

Kruskal 用来求:

最小生成树(MST)

要求:无向连通带权图

和 Prim 一样。

和 Prim 的区别

Prim:长点

从一个点开始,不断加入新的点

Kruskal:挑边

每次挑最短的边,直到所有点连通

  1. 核心概念

例如:

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

会形成环

所以:不能加入->结束

核心思想

按边权从小到大排序
能加就加
形成环就跳过

  1. 为什么要并查集?

问题:如何知道:

加入一条边

会不会形成环?

例如:已经有:

A-B

B-C

现在:A-C加入。

其实:A已经能到C,所以:加入以后形成环

用:并查集(Union Find)判断:两个点是不是已经连通。

  1. 并查集

维护: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

三个已经连通。

  1. 并查集代码

初始化:

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;
}

}
//把两个树合并到一块

  1. 边结构
    struct Edge
    {
    int u;
    int v;
    int w;
    };

表示:

u

v

权值w

  1. 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)

  1. 前提条件

DFS(Depth First Search)是一种图和树的遍历算法。

主要解决:

① 图遍历

② 树遍历

③ 连通块

④ 判断是否连通

⑤ 回溯搜索

⑥ 岛屿问题

⑦ 路径搜索

适用于:树、图(有向图、无向图)、迷宫

  1. 核心思想

一条路走到底
走不通再回来

例如:

    0
  /   \
 1     2
/

3

DFS访问顺序:

0

1

3

2

一直往下面钻

钻不动返回上一层

继续其它分支

这就是:回溯(Backtracking)

  1. 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)

  1. DFS递归树

例如:

    0
  /   \
 1     2
/

3

真正调用过程:

dfs(0)

├────dfs(1)

│ │

│ └────dfs(3)

└────dfs(2)

特别容易理解。

  1. 图的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;
}

}

  1. 每一句代码解释

第一句:

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;

表示:

继续看下一个邻居

  1. 完整例子

图:

    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

  1. 树的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

  1. DFS复杂度

邻接表:

每个点访问一次

每条边访问一次

时间:O(V+E)

V:顶点

E:边

空间:递归栈:O(V)

  1. 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)

应用:

图遍历
连通块
岛屿问题
回溯
树遍历