forked from soulmachine/acm-cheat-sheet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchapGraph.tex
2585 lines (2158 loc) · 85.1 KB
/
chapGraph.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{图}
稠密图适合用邻接矩阵来表示。
\begin{Codex}[label=am_graph.c]
/** 顶点数最大值. */
#define MAX_NV 100
/** 边的权值类型,可以为int, float, double. */
typedef int graph_weight_t;
#define GRAPH_INF INT_MAX
/**
* @struct 图,用邻接矩阵(Adjacency Matrix).
*/
typedef struct graph_t {
int nv; /* 顶点数 */
int ne; /* 边数 */
/* 邻接矩阵,存放边的信息,如权重等 */
graph_weight_t matrix[MAX_NV][MAX_NV];
} graph_t;
\end{Codex}
稀疏图适合用邻接表来表示。
\section{图的深搜} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
图的深度优先搜索的代码框架如下:
\begin{Codex}[label=graph_dfs.c]
#ifndef __cplusplus
typedef char bool;
#define false 0
#define true 1
#endif
/**
* @brief 图的深度优先搜索代码框架,搜索边.
* @param[in] g 图
* @param[in] u 出发顶点
* @param[in] visited 边的访问历史记录
* @return 无
* @remark 在使用的时候,为了降低递归的内存占用量,可以把
* g, visited 抽出来作为全局变量
*/
void dfs(const graph_t *g, int u, bool visited[][MAX_NV]) {
int v;
for(v = 0; v < g->nv; v++) if(g->matrix[u][v] && !visited[u][v]) {
visited[u][v] = visited[v][u] = true; // 无向图用这句
// visited_edges[u][v] = true; // 有向图用这句
dfs(g, v, visited);
// 这里写逻辑代码
// printf("%d %d\n", u, v);
}
}
/**
* @brief 图的深度优先搜索代码框架,搜索顶点.
* @param[in] g 图
* @param[in] u 出发顶点
* @param[in] visited 顶点的访问历史记录
* @return 无
* @remark 在使用的时候,为了降低递归的内存占用量,可以把
* g, visited 抽出来作为全局变量
*/
void dfs(const graph_t *g, int u, bool visited[MAX_NV]) {
int v;
visited[u] = true;
for(v = 0; v < g->nv; v++) if(g->matrix[u][v] && !visited[v]) {
dfs(g, v, visited);
// 这里写逻辑代码
// printf("%d %d\n", u, v);
}
}
\end{Codex}
\subsection{Satellite Photographs}
\subsubsection{描述}
Farmer John purchased satellite photos of $W \times H$ pixels of his farm ($1 \leq W \leq 80, 1 \leq H \leq 1000$) and wishes to determine the largest 'contiguous' (connected) pasture. Pastures are contiguous when any pair of pixels in a pasture can be connected by traversing adjacent vertical or horizontal pixels that are part of the pasture. (It is easy to create pastures with very strange shapes, even circles that surround other circles.)
Each photo has been digitally enhanced to show pasture area as an asterisk ('*') and non-pasture area as a period ('.'). Here is a $10 \times 5$ sample satellite photo:
\begin{Code}
..*.....**
.**..*****
.*...*....
..****.***
..****.***
\end{Code}
This photo shows three contiguous pastures of 4, 16, and 6 pixels. Help FJ find the largest contiguous pasture in each of his satellite photos.
\subsubsection{输入}
Line 1: Two space-separated integers: $W$ and $H$
Lines 2..H+1: Each line contains $W$ "*" or "." characters representing one raster line of a satellite photograph.
\subsubsection{输出}
Line 1: The size of the largest contiguous field in the satellite photo.
\subsubsection{样例输入}
\begin{Code}
10 5
..*.....**
.**..*****
.*...*....
..****.***
..****.***
\end{Code}
\subsubsection{样例输出}
\begin{Code}
16
\end{Code}
\subsubsection{分析}
这是一个平面的二维地图,把地图上的每个点当成隐式图上的一个顶点,每个顶点有上下左右四个邻接点。在这个隐式图上进行深搜。
\subsubsection{代码}
\begin{Codex}[label=satellite_photographs.c]
/* POJ 3051 Satellite Photographs, http://poj.org/problem?id=3051 */
#include <stdio.h>
#include <string.h>
#define MAXH 1000
#define MAXW 80
int H, W; /* H行W列 */
char map[MAXH+2][MAXW+2];/* 上下左右加一圈'.'可以防止越界 */
int count;
void dfs(int x, int y) {
/* 加了一圈'.'可以防止越界,因此不需要判断越界 */
if (map[x][y] == '.') return;
map[x][y] = '.'; /* 标记(x,y)已访问过,起到去重作用 */
count++;
dfs(x + 1, y);
dfs(x - 1, y);
dfs(x, y + 1);
dfs(x, y - 1);
}
int main() {
int i, j, max;
memset(map, '.', sizeof(map));
scanf("%d%d", &W, &H); /* H是行数,W是列数 */
for(i = 1; i <= H; ++i) {
char line[MAXW+1];
scanf("%s", line);
strncpy(&map[i][1], line, W);
}
max = 0;
for (i = 1; i <= H; i++) {
for (j = 1; j <= W; j++) {
if (map[i][j] == '*') {
count = 0;
dfs(i, j);
}
if (count > max) max = count;
}
}
printf("%d\n", max);
return 0;
}
\end{Codex}
\subsubsection{相关的题目}
与本题相同的题目:
\begindot
\item POJ 3051 Satellite Photographs, \myurl{http://poj.org/problem?id=3051}
\myenddot
与本题相似的题目:
\begindot
\item POJ 3620 Avoid The Lakes, \myurl{http://poj.org/problem?id=3620} \\ 参考代码 \myurl{https://gist.github.com/soulmachine/6761537}
\myenddot
\subsection{John's trip}
\subsubsection{Description}
Little Johnny has got a new car. He decided to drive around the town to visit his friends. Johnny wanted to visit all his friends, but there was many of them. In each street he had one friend. He started thinking how to make his trip as short as possible. Very soon he realized that the best way to do it was to travel through each street of town only once. Naturally, he wanted to finish his trip at the same place he started, at his parents' house.
The streets in Johnny's town were named by integer numbers from 1 to $n, n < 1995$. The junctions were independently named by integer numbers from 1 to $m, m <= 44$. No junction connects more than 44 streets. All junctions in the town had different numbers. Each street was connecting exactly two junctions. No two streets in the town had the same number. He immediately started to plan his round trip. If there was more than one such round trip, he would have chosen the one which, when written down as a sequence of street numbers is lexicographically the smallest. But Johnny was not able to find even one such round trip.
Help Johnny and write a program which finds the desired shortest round trip. If the round trip does not exist the program should write a message. Assume that Johnny lives at the junction ending the street appears first in the input with smaller number. All streets in the town are two way. There exists a way from each street to another street in the town. The streets in the town are very narrow and there is no possibility to turn back the car once he is in the street
\subsubsection{Input}
Input file consists of several blocks. Each block describes one town. Each line in the block contains three integers $x; y; z$, where $x > 0$ and $y > 0$ are the numbers of junctions which are connected by the street number $z$. The end of the block is marked by the line containing $x = y = 0$. At the end of the input file there is an empty block, $x = y = 0$.
\subsubsection{Output}
Output one line of each block contains the sequence of street numbers (single members of the sequence are separated by space) describing Johnny's round trip. If the round trip cannot be found the corresponding output block contains the message "Round trip does not exist."
\subsubsection{Sample Input}
\begin{Code}
1 2 1
2 3 2
3 1 6
1 2 5
2 3 3
3 1 4
0 0
1 2 1
2 3 2
1 3 3
2 4 4
0 0
0 0
\end{Code}
\subsubsection{Sample Output}
\begin{Code}
1 2 3 5 4 6
Round trip does not exist.
\end{Code}
\subsubsection{分析}
欧拉回路。
如果能从图的某一顶点出发,每条边恰好经过一次,这样的路线称为\textbf{欧拉道路}(Eulerian Path)。
如果还能够回到起点,这样的路线称为\textbf{欧拉回路}(Eulerian Circuit)。
对于无向图G,当且仅当G是连通的,且最多有两个奇点,则存在欧拉道路。
如果有两个奇点,则必须从其中一个奇点出发,到另一个奇点终止。
如果没有奇点,则一定存在一条欧拉回路。
对于有向图G,当且仅当G是连通的,且每个点的入度等于出度,则存在欧拉回路。
如果有两个顶点的入度与出度不相等,且一个顶点的入度比出度小1,另一个顶点的入度比出度大1,此时,
存在一条欧拉道路,以前一个顶点为起点,以后一个顶点为终点。
\subsubsection{代码}
\begin{Codex}[label=round_trip.c]
/* POJ 1041 John's trip, http://poj.org/problem?id=1041 */
#include <stdio.h>
#include <string.h>
#define max(a,b) ((a)>(b)?(a):(b))
#ifndef __cplusplus
typedef char bool;
#define false 0
#define true 1
#endif
#define MAX_NV 45
#define MAX_NE 1996
/**
* @struct 图,邻接矩阵的变种.
*/
typedef struct graph_t {
int nv; /* 顶点数 */
int ne; /* 边数 */
/* G[点][边] = 点,这样是为了能方便让边lexicographically输出 */
int matrix[MAX_NV][MAX_NE];
} graph_t;
graph_t G;
bool visited[MAX_NE]; /* 边是否已访问 */
int degree[MAX_NV]; /* 点的度 */
int stack[MAX_NE], top; /* 栈,用于输出 */
void stack_print(const int stack[]) {
do {
printf("%d ", stack[top]);
} while (--top);
}
void euler(int u) {
int e;
for (e = 1; e <= G.ne; e++) {
if (!visited[e] && G.matrix[u][e]) { //若相邻边未访问过
visited[e] = true;
euler(G.matrix[u][e]);
stack[++top] = e;
}
}
}
int main() {
int i;
int x, y, z, start;
while (scanf("%d%d", &x, &y) && x && y) {
top = 0;
memset(visited, false, sizeof(visited));
memset(degree, 0, sizeof(degree));
memset(&G, 0, sizeof(G));
start = x < y ? x : y;
scanf("%d", &z);
G.ne = max(G.ne, z);
G.nv = max(G.nv, max(x, y));
G.matrix[x][z] = y;
G.matrix[y][z] = x;
++degree[x];
++degree[y];
while (scanf("%d%d", &x, &y) && x && y) {
scanf("%d", &z);
G.ne = max(G.ne, z);
G.nv = max(G.nv, max(x, y));
G.matrix[x][z] = y;
G.matrix[y][z] = x;
++degree[x];
++degree[y];
}
/* 欧拉回路形成的条件之一,判断结点的度是否为偶数 */
bool flag = true;
for (i = 1; i <= G.nv; i++) {
if (degree[i] & 1) {
flag = false;
break;
}
}
if (!flag) {
printf("Round trip does not exist.\n");
} else {
euler(start);
stack_print(stack);
printf("\n");
}
}
return 0;
}
\end{Codex}
\subsubsection{相关的题目}
与本题相同的题目:
\begindot
\item POJ 1041 John's trip, \myurl{http://poj.org/problem?id=1041}
\myenddot
与本题相似的题目:
\begindot
\item 《算法竞赛入门经典》\footnote{刘汝佳,算法竞赛入门经典,清华大学出版社,2009} 第111页6.4.4节
\item UVa 10054 The Necklace, \myurl{http://t.cn/zRwqcRp}
\item UVa 10129 Play on Words, \myurl{http://t.cn/zTInBDX}
\myenddot
\subsection{The Necklace}
\subsubsection{描述}
My little sister had a beautiful necklace made of colorful beads. Two successive beads in the
necklace shared a common color at their meeting point. The figure below shows a segment of
the necklace:
\centerline{\fbox{\includegraphics[width=240pt]{uva10054.png}}}
But, alas! One day, the necklace was torn and the beads were all scattered over the floor.
My sister did her best to recollect all the beads from the floor, but she is not sure
whether she was able to collect all of them. Now, she has come to me for help. She wants
to know whether it is possible to make a necklace using all the beads she has in the same
way her original necklace was made and if so in which order the bids must be put.
Please help me write a program to solve the problem.
\subsubsection{Input}
The input contains T test cases. The first line of the input contains the integer T.
The first line of each test case contains an integer $N(5 \leq N \leq 1000)$ giving the number of beads
my sister was able to collect. Each of the next N lines contains two integers describing
the colors of a bead. Colors are represented by integers ranging from 1 to 50.
\subsubsection{Output}
For each test case in the input first output the test case number as shown in the sample output. Then
if you apprehend that some beads may be lost just print the sentence ``some beads may be lost" on a
line by itself. Otherwise, print N lines with a single bead description on each line. Each bead
description consists of two integers giving the colors of its two ends. For $1 \leq i \leq N_1$, the second integer
on line i must be the same as the first integer on line i + 1. Additionally, the second integer
on line N must be equal to the first integer on line 1. Since there are many solutions, any one
of them is acceptable.
Print a blank line between two successive test cases.
\subsubsection{Sample Input}
\begin{Code}
2
5
1 2
2 3
3 4
4 5
5 6
5
2 1
2 2
3 4
3 1
2 4
\end{Code}
\subsubsection{Sample Output}
\begin{Code}
Case #1
some beads may be lost
Case #2
2 1
1 3
3 4
4 2
2 2
\end{Code}
\subsubsection{分析}
欧拉回路。
注意顶点可以有自环。
\subsubsection{代码}
\begin{Codex}[label=eulerian_circuit.c]
#include <stdio.h>
#include<string.h>
#define MAXN 51 // 顶点最大个数
int G[MAXN][MAXN];
int visited_vertices[MAXN];
int visited_edges[MAXN][MAXN];
int count[MAXN]; // 顶点的度
void dfs(const int u) {
int v;
visited_vertices[u] = 1;
for(v = 0; v < MAXN; v++) if(G[u][v] && !visited_vertices[v]) {
dfs(v);
}
}
/*
* @brief 欧拉回路,允许自环和重复边
* @param[in] u 起点
* @return 无
*/
void euler(const int u){
int v;
for(v = 0; v < MAXN; ++v) if(G[u][v]){
--G[u][v]; --G[v][u]; // 这个技巧,即有visited的功能,又允许重复边
euler(v);
// 逆向打印,或者存到栈里再打印
printf("%d %d\n", u, v);
}
}
int main() {
int T, N, a, b;
int i;
int cases=1;
scanf("%d",&T);
while(T--) {
int flag = 1; // 结点的度是否为偶数
int flag2 = 1; // 图是否是连通的
memset(G, 0, sizeof(G));
memset(count, 0, sizeof(count));
scanf("%d",&N);
for(i = 0; i < N; ++i){
scanf("%d %d", &a, &b);
++G[a][b];
++G[b][a];
++count[a];
++count[b];
}
printf("Case #%d\n", cases++);
// 欧拉回路形成的条件之一,判断结点的度是否为偶数
for(i=0; i<MAXN; ++i) {
if(count[i] & 1){
flag = 0;
break;
}
}
// 检查图是否连通
if(flag) {
memset(visited_vertices, 0, sizeof(visited_vertices));
memset(visited_edges, 0, sizeof(visited_edges));
for(i=0; i< MAXN; ++i)
if(count[i]) {
dfs(i);
break;
}
for(i=0; i< MAXN; ++i){
if(count[i] && !visited_vertices[i]) {
flag2 = 0;
break;
}
}
}
if (flag && flag2) {
for(i = 0; i < MAXN; ++i) if(count[i]){
euler(i);
break;
}
} else {
printf("some beads may be lost\n");
}
if(T > 0) printf("\n");
}
return 0;
}
\end{Codex}
\subsubsection{相关的题目}
与本题相同的题目:
\begindot
\item UVa 10054 The Necklace, \myurl{http://t.cn/zRwqcRp}
\myenddot
与本题相似的题目:
\begindot
\item 《算法竞赛入门经典》\footnote{刘汝佳,算法竞赛入门经典,清华大学出版社,2009} 第111页6.4.4节
\item POJ 1041 John's trip, \myurl{http://poj.org/problem?id=1041}
\item UVa 10129 Play on Words, \myurl{http://t.cn/zTInBDX}
\myenddot
\section{图的广搜} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{最小生成树} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
“最小”指的是边的权值之和最小。
构造最小生成树(Minimum Spanning Tree, MST)有多种算法。其中多数算法利用了最小生成树的一个性质(简称为MST性质):假设$N=(V, E)$是一个连通网,$U$是顶点集$V$的一个非空子集。若$(u, v)$是一条具有最小权值的边,其中$u \in U, v \in V-U$,则必存在一颗包含边$(u, v)$的最小生成树。
Prim算法和Kruskal算法是两个利用MST性质构造最小生成树的算法。它们都属于贪心法。
\subsection{Prim算法}
假设$N=(V, E)$是一个连通网,$TE$是$N$上最小生成树中边的集合。算法从$U={u_0}(u_0 \in V), TE=\{\}$开始,重复执行下述操作:在所有$u \in U, v \in V-U$的边$(u, v) \in E$中找一条代价最小的边$(u_0, v_0)$并入集合$TE$,同时$v_0$并入U,直至$U=V$为止。此时$TE$中必有$n-1$条边,则$T=(V, TE)$为$N$的最小生成树。
为实现这个算法需附设一个数组\fn{closedge},以记录从$U$到$V-U$具有最小代价的边。对每个顶点$v_i \in V-U$,在辅助数组中存在一个相应分量\fn{closedge[i-1]},它包括两个域,其中\fn{lowcost}存储该边上的权。显然,$closedge[i].lowcost=\min\left\{cost(u, v_i), u \in U\right\}$。\fn{adjvex}域存储该边依附的在U中的顶点。
图 \ref{fig:prim}所示为按Prim算法构造网的一棵最小生成树的过程,在构造过程中辅助数组中各分量值的变化如表\ref{tab:prim}所示。
\begin{center}
\includegraphics[width=240pt]{prim1.png}\\
\includegraphics[width=240pt]{prim2.png}\\
\includegraphics[width=240pt]{prim3.png}\\
\figcaption{Prim算法构造最小生成树的过程}\label{fig:prim}
\end{center}
\begin{center}
\tabcaption{构造最小生成树过程中辅助数组的变化}
\label{tab:prim}
\begin{tabular}{|c|cccccccc|}
\hline
\textbf{\diagbox{closedge}{i}} & \textbf{1} & \textbf{2} & \textbf{3} & \textbf{4}& \textbf{5}& \textbf{U}& \textbf{U-V}& \textbf{k}\\
\hline
adjvex & $v_0$ & $v_0$ & $v_0$ & & & $v_0$ & $\{v_1,v_2,v_3,v_4,v_5\}$ & \multirow{2}{*}{2} \\
lowcost & 6 & 1 & 5 & & & & & \\
\hline
adjvex & $v_2$ & & $v_1$ & $v_2$ & $v_2$ & $\{v_0,v_2\}$ & $\{v_1,v_3,v_4,v_5\}$ & \multirow{2}{*}{5} \\
lowcost & 5 & 0 & 5 & 6 & 4 & & & \\
\hline
adjvex & $v_2$ & & $v_6$ & $v_2$ & & $\{v_0,v_2,v_5\}$ & $\{v_1,v_3,v_4\}$ & \multirow{2}{*}{3} \\
lowcost & 5 & 0 & 2 & 6 & 0 & & & \\
\hline
adjvex & $v_2$ & & & $v_2$ & & $\{v_0,v_2,v_5,v_3\}$ & $\{v_1,v_4\}$ & \multirow{2}{*}{1} \\
lowcost & 5 & 0 & 0 & 6 & 0 & & & \\
\hline
adjvex & & & & $v_1$ & & $\{v_0,v_2,v_5,v_3,v_1\}$ & $\{v_4\}$ & \multirow{2}{*}{4} \\
lowcost & 0 & 0 & 0 & 3 & 0 & & & \\
\hline
adjvex & & & & & & $\{v_0,v_2,v_5,v_3,v_1,v_4\}$ & $\{\}$ & \multirow{2}{*}{} \\
lowcost & 0 & 0 & 0 & 0 & 0 & & & \\
\hline
\end{tabular}
\end{center}
\subsubsection{代码}
\begin{Codex}[label=mgraph_prim1.c]
#include <stdio.h>
#include <stdlib.h> /* for malloc() */
#include <limits.h> /* for INT_MAX */
/** 顶点数的最大值*/
#define MAX_VERTICES_NUM 100
/** 边的权值,对无权图,用0或1表示是否相邻;对有权图,则为权值. */
typedef int graph_weight_t;
#define GRAPH_INF INT_MAX
/**
*@struct
*@brief 邻接矩阵.
*/
typedef struct mgraph_t {
int nv; /* 顶点数*/
int ne; /* 边数*/
/* 邻接矩阵,存放边的信息,如权重等*/
graph_weight_t matrix[MAX_VERTICES_NUM][MAX_VERTICES_NUM];
} mgraph_t;
mgraph_t g;
typedef struct closedge_t {
int adjvex; /* 弧头,属于U */
/* 边 adjvex->本下标 的权值,-GRAPH_INF表示已经加入U */
graph_weight_t lowcost;
} closedge_t;
/*
* @brief 在V-E集合中寻找最小的边
* @param[in] closedge MST中的边,起点为adjvex,终点为本下标
* @param[in] n closedge数组的长度
* @return 找到了则返回弧尾的下标,V-U为空集则返回-1,表示终止
*/
static int min_element(const closedge_t closedge[], int n) {
int i;
int min_value = GRAPH_INF;
int min_loc = -1;
for (i = 0; i < n; i++)
if (closedge[i].lowcost > -GRAPH_INF) {
if (min_value > closedge[i].lowcost) {
min_value = closedge[i].lowcost;
min_loc = i;
}
}
return min_loc;
}
/**
* @brief Prim算法,求图的最小生成树.
* @param[in] g 图对象的指针
* @return MST的边的权值之和
*/
graph_weight_t mgraph_prim(const mgraph_t *g) {
graph_weight_t sum = 0; /* 权值之和 */
int i, j;
int u = 0; /* 从0号顶点出发 */
const int n = g->nv;
/* closedge[n],记录从顶点集U到V-U的边*/
closedge_t* const closedge = (closedge_t*) malloc(n * sizeof(closedge_t));
/* 辅助数组初始化*/
for (i = 0; i < n; i++) if (i != u) {
closedge[i].adjvex = u;
closedge[i].lowcost = g->matrix[u][i];
}
closedge[u].lowcost = -GRAPH_INF; /* 初始, U={u} */
for (i = 0; i < n; i++) if (i != u) { /* 其余的n-1个顶点*/
/* 求出TE的下一个顶点k */
const int k = min_element(closedge, n);
/* 输出此边 closedge[k].adjvex --> k */
printf("%c - %c : %d\n", 'A' + closedge[k].adjvex, 'A' + k,
g->matrix[closedge[k].adjvex][k]);
sum += g->matrix[closedge[k].adjvex][k];
// sum += closedge[k].lowcost; // 等价
closedge[k].lowcost = -GRAPH_INF; /* 顶点k并入U,表示此边加入TE */
/* 更新k的邻接点的值,不相邻为无穷大*/
for (j = 0; j < n; j++) {
const graph_weight_t w = g->matrix[k][j];
if (w < closedge[j].lowcost) {
closedge[j].adjvex = k;
closedge[j].lowcost = w;
}
}
}
free(closedge);
return sum;
}
/** 读取输入,构建图. */
void read_graph() {
int i, j, k, m, n;
/* 读取节点和边的数目 */
scanf("%d%d", &m, &n);
getchar(); // 消耗回车键
g.nv = m;
g.ne = n;
/* 初始化图,所有节点间距离为无穷大 */
for (i = 0; i < m; i++) {
for (j = 0; j < m; j++) {
g.matrix[i][j] = GRAPH_INF;
}
}
/* 读取边信息 */
for (k = 0; k < n; k++) {
char chx, chy;
int w;
scanf("%c %c %d", &chx, &chy, &w);
getchar();
i = chx - 'A';
j = chy - 'A';
g.matrix[i][j] = w;
g.matrix[j][i] = w;
}
}
/* test
输入数据:
7 11
A B 7
A D 5
B C 8
B D 9
B E 7
C E 5
D E 15
D F 6
E F 8
E G 9
F G 11
输出:
A - D : 5
D - F : 6
A - B : 7
B - E : 7
E - C : 5
E - G : 9
Total:39
*/
int main() {
read_graph();
/* 求解最小生成树 */
printf("Total:%d\n", mgraph_prim(&g));
return 0;
}
\end{Codex}
\subsubsection{算法分析}
假设网中有$n$个顶点,则第一个进行初始化的循环语句的频度为$n$,第二个循环语句的频度为$n-1$。其中有两个内循环:其一是在\fn{closedge[v].lowcost}中求最小值,其频度为$n-1$;其二是重新选择具有最小代价的边,其频度为$n$。因此Prim算法的时间复杂度为$O(n^2)$,与网中边数无关,因此适用于求边稠密的图的最小生成树。
Prim算法的另一种实现是使用小根堆,其流程是:小根堆中存储一个端点在生成树中,另一个端点不在生成树的边,每次从小根堆的堆顶可选出权值最小的边$(u, v)$,将其从堆中推出,加入生成树中。然后将新出现的所有一个端点在生成树中,一个端点不在生成树的边都插入小根堆中。下一轮迭代中,下一条满足要求的边又上升到堆顶。如此重复$n-1$次,最后建立起该图的最小生成树。该算法的C代码实现如下。
\subsubsection{代码}
\begin{Codex}[label=mgraph_prim2.c]
#include <stdio.h>
#include <stdlib.h> /* for malloc() */
#include <limits.h> /* for INT_MAX */
/** 顶点数的最大值*/
#define MAX_VERTICES_NUM 100
/** 边的权值,对无权图,用0或1表示是否相邻;对有权图,则为权值. */
typedef int graph_weight_t;
#define GRAPH_INF INT_MAX
/**
*@struct
*@brief 邻接矩阵.
*/
typedef struct mgraph_t {
int nv; /* 顶点数*/
int ne; /* 边数*/
/* 邻接矩阵,存放边的信息,如权重等*/
graph_weight_t matrix[MAX_VERTICES_NUM][MAX_VERTICES_NUM];
} mgraph_t;
mgraph_t g;
/**
* @struct 边
*/
typedef struct edge_t{
int tail; /** 弧尾, from */
int head; /** 弧头, to */
graph_weight_t w; /** 权值 */
}edge_t;
static int edge_cmp(const edge_t *e1, const edge_t *e2) {
return e1->w - e2->w;
}
typedef edge_t heap_elem_t; // 元素的类型
/* 等价于复制粘贴,这里为了节约篇幅,使用include,在OJ上提交时请用复制粘贴 */
#include "heap.c" /* 见“树->堆”这节 */
/**
* @brief Prim算法,求图的最小生成树.
* @param[in] g 图对象的指针
* @return MST的边的权值之和
*/
int mgraph_prim(const mgraph_t *g){
graph_weight_t sum = 0; /* 权值之和 */
int u = 0; /* 从0号顶点出发 */
int i, count = 1;
edge_t e;
heap_t *h = heap_create(g->ne, edge_cmp);
const int n = g->nv;
/* 判断顶点是否已经加入最小生成树*/
int* U = (int *)malloc(n * sizeof(int));
for(i = 0; i < n; i++) U[i] = 0;
/* 开始顶点加入U(所以count初始为1) */
U[u] = 1;
while (count < n) {
int v;
for(v = 0; v < n; v++) if(!U[v]) { /* 若v不在生成树,(u,v)加入堆*/
e.tail = u;
e.head = v;
/* tail在树内,head不在树内*/
e.w = g->matrix[u][v];
heap_push(h, e);
}
while(!heap_empty(h) && count < n) {
/* 从堆中退出最小权值边,存入ed */
e = heap_top(h); heap_pop(h);
if(!U[e.head]) {
/* 输出生成树TE的边,即此边加入TE */
printf("%c - %c: %d\n", 'A' + e.tail, 'A' + e.head,
g->matrix[e.tail][e.head]);
sum += g->matrix[e.tail][e.head];
u = e.head;
/* u并入到生成树的顶点集合U */
U[u] = 1;
count++;
break;
}
}
}
free(U);
heap_destroy(h);
return sum;
}
// ...
\end{Codex}
\subsubsection{算法分析}
该算法迭代次数为$O(n)$,每次迭代将平均$e/n$条边插入最小堆中,$e$条边从堆中删除,堆的插入和删除操作时间复杂度均为$O(\log_2 e)$,则总的时间复杂度为 $O(e\log_2e)$。
\subsection{Kruskal算法}
\label{sec:kruskal}
假设连通网$N={V, E}$,则令最小生成树的初始状态为只有$n$个顶点而无边的非连通图$T=(V, {})$,图中每个顶点自成一个连通分量。在$E$中选择代价最小的边,若该边依附的顶点落在$T$中不同的连通分量上,则将此边加入到$T$中,否则舍去此边而选择下一条代价最小的边。依次类推,直至T中所有顶点都在同一连通分量上为止。
图\ref{fig:kruskal}所示为Kruskal算法构造一棵最小生成树的过程。
\begin{center}
\includegraphics[width=240pt]{kruskal1.png}\\
\includegraphics[width=240pt]{kruskal2.png}\\
\figcaption{Kruskal算法构造最小生成树的过程}\label{fig:kruskal}
\end{center}
下面是Kruskal算法的C语言实现。
\subsubsection{代码}
\begin{Codex}[label=kruskal.c]
#include <stdio.h>
#include <stdlib.h> /* for malloc() */
#include <limits.h> /* for INT_MAX */
/* 等价于复制粘贴,这里为了节约篇幅,使用include,在OJ上提交时请用复制粘贴 */
#include "ufs.c" /* 见“树->并查集”这节 */
#define MAX_VERTICES_NO 11 /* 顶点编号最大值 */
#define MAX_EDGES 100 /* 最大边数 */
/** 边的权值,对无权图,用0或1表示是否相邻;对有权图,则为权值. */
typedef int graph_weight_t;
/**
* @struct 无向图的边.
*/
typedef struct edge_t{
int u; /** 顶点编号 */
int v; /** 顶点编号 */
graph_weight_t w; /** 权值 */
} edge_t;
edge_t edges[MAX_EDGES];
static int edge_cmp(const edge_t *e1, const edge_t *e2) {
return e1->w - e2->w;
}
typedef edge_t heap_elem_t; // 元素的类型
/* 等价于复制粘贴,这里为了节约篇幅,使用include,在OJ上提交时请用复制粘贴 */
#include "heap.c" /* 见“树->堆”这节 */
/*
* @brief Kruskal算法,求图的最小生成树.
* @param[in] edges 边的数组
* @param[in] n 边数,一定要大于或等于(顶点数-1)
* @param[in] m 顶点数
* @return MST的边的权值之和
*/
graph_weight_t kruskal(const edge_t edges[], int n, int m) {
int i;
graph_weight_t sum = 0;
heap_t *h = heap_create(n, edge_cmp);
ufs_t *s = ufs_create(MAX_VERTICES_NO); /* 并查集,0位置未用 */
if (n < m - 1) return -1;
/* 把所有边插入堆中*/
for (i = 0; i < n; i++) {
heap_push(h, edges[i]);
}
for (i = 0; i < n; i++) {
/* 从堆中退出最小权值边 */
const edge_t e = heap_top(h);
int u, v;
heap_pop(h);
/* 取两顶点所在集合的根*/
u = ufs_find(s, e.u);
v = ufs_find(s, e.v);
if (u != v) { /* 不是同一集合,说明不连通*/
ufs_union(s, u, v); /* 合并,连通成一个分量*/
/* 输出生成树TE的边,即此边加入TE */
printf("%d - %d\n", e.u, e.v);
sum += e.w;
}
}
heap_destroy(h);
ufs_destroy(s);
return sum;
}
static int edge_cmp1(const void *e1, const void *e2) {
const edge_t* const ee1 = (const edge_t *)e1;
const edge_t* const ee2 = (const edge_t *)e2;
return ee1->w - ee2->w;
}
/** Kruskal算法,快排+并查集. */
graph_weight_t kruskal1(edge_t edges[], int n, int m) {
int i;
graph_weight_t sum = 0;
ufs_t *s = ufs_create(MAX_VERTICES_NO); /* 并查集,0位置未用 */
if (n < m - 1) return -1;
qsort(edges, n, sizeof(edge_t), edge_cmp1);
for (i = 0; i < n; i++) {
/* 从堆中退出最小权值边,存入ed */
const edge_t e = edges[i];
/* 取两顶点所在集合的根*/
const int u = ufs_find(s, e.u);
const int v = ufs_find(s, e.v);
if (u != v) { /* 不是同一集合,说明不连通*/
ufs_union(s, u, v); /* 合并,连通成一个分量*/
/* 输出生成树TE的边,即此边加入TE */
printf("%d - %d\n", e.u, e.v);
sum += e.w;
}
}
ufs_destroy(s);
return sum;
}
/* test
输入数据:
7 11
0 1 7
0 3 5
1 2 8
1 3 9
1 4 7
2 4 5
3 4 15
3 5 6
4 5 8
4 6 9
5 6 11
输出: