『普里姆算法』和『克鲁斯卡尔算法』,它们的目的都是生成『最小生成树』,它们两者的实现原理是比较相似的,只不过一个通过边,而另一个主要是通过顶点来实现的,下面我们就一个一个来进行介绍。

普里姆算法

普里姆算法(Prim),是图论中的一种算法,可在加权连通图里搜索最小生成树,意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小,我们通过一个简单的示例来了解一下为什么需要『普里姆算法』,如下

20210601141614

图中的顶点我们可以将其想象成一个一个的村庄,而我们的目标就是让所有的村庄都连通起来,并且消耗的资源最少,当然实现的方式有很多种,比如下面这样

20210601141633

通过计算可以发现,它的成本为 11 + 26 + 20 + 22 + 18 + 21 + 24 + 19 = 161,不过如果我们仔细观察的话,可以发现这种连通方式是十分消耗资源的,所以我们稍微调整一下,就有了下面这种方式

20210601141717

通过计算发现其成本为 8 + 12 + 10 + 11 + 17 + 19 + 16 + 7 = 100,这样看起来似乎成本小了不少,但是有没有消耗更少的连通方式呢,方法是有的

20210601141737

这一次的成本为 8 + 12 + 10 + 11 + 16 + 19 + 16 + 7 = 99,可以发现,这一次便是最优的解决方式,那么问题就来了,我们该如何从多种方式当中来选取最优的方案呢,所以这就有了普里姆算法,下面我们就通过一个示例来了解,到底什么是普里姆算法,如下

20210601141757

我们选择从 0 开始出发构造我们的 MST,我们约定,蓝色顶点为蓝点集合(表示暂时还未遍历的点),黑色顶点为黑点集合(表示已经遍历过的点),红色边为最短边,灰色边为淘汰边,下面我们就开始从 0 进行遍历,所以出发点由蓝色变成黑色,如下

20210601141834

下面我们把和顶点 0 与相邻顶点之间的连线改变成紫色

20210601141906

我们第一步就是找与它相邻的边当中权值最小的,可以很明显的发现,是顶点 2,所以我们的目标就是 2 号顶点

20210601141931

下面一步就比较复杂,因为和顶点 2 相邻的有 1,3,4,5,但是 1 和 0 相连,3 也和 0 相连,但是我们在这里约定,『若是一个蓝点与多个黑点有边相连,则取权值最小的边作为紫边』,所以这时我们就需要比较 (0, 1) 和 (1, 2) 之间的边的权值,可以发现 (1, 2) 的权值更小,同理 (0, 3) 的边比 (2, 3) 的边的权值也更小,所以就有了如下的情况

20210601141948

我们选择剔除掉 (0, 1) 和 (2, 3) 之间的边(因为它们的权值更大),接着我们将与 2 相连的边调整为了紫色,接下来同理,我们继续寻找紫边当中权值最小的,可以发现是 (2, 5),所以我们的目标就是 5 号顶点,如下

20210601142016

接下来同理,我们比较 (0, 3),(3, 5),(2, 4) 和 (4, 5),结果如下

20210601142032

继续寻找权值最小的边,为 (5, 3),所以变成如下的情况

20210601142047

接下来同理,继续选择权值较小的边,因为两边一致,所以我们随便挑选一条

20210601142104

继续比较 (1, 4) 和 (2, 4),可以发现 (1, 4) 的更小

20210601142119

所以最终的结果如下

20210601142135

下面我们看如何用代码来进行实现,大致思想是

  • 设图 G 顶点集合为 U,首先任意选择图 G 中的一点作为起始点 a,将该点加入集合 V
  • 再从集合 U - V 中找到另一点 b 使得点 b 到 V 中任意一点的权值最小,此时将 b 点也加入集合 V
  • 以此类推,现在的集合 V = { a, b },再从集合 U - V 中找到另一点 c 使得点 c 到 V 中任意一点的权值最小,此时将 c 点加入集合 V,直至所有顶点全部被加入 V
  • 此时就构建出了一颗 MST,因为有 N 个顶点,所以该 MST 就有 N - 1 条边,每一次向集合 V 中加入一个点,就意味着找到一条 MST 的边

在此之前,我们可以将上面的图片当中的数据转化为图片的格式。

20210601142221

代码实现

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
/**
* Java: prim算法生成最小生成树(邻接矩阵)
*
* @author skywang
* @date 2014/04/23
*/

import java.io.IOException;
import java.util.Scanner;

public class MatrixUDG {

private char[] mVexs; // 顶点集合
private int[][] mMatrix; // 邻接矩阵
private static final int INF = Integer.MAX_VALUE; // 最大值

/*
* 创建图(自己输入数据)
*/
public MatrixUDG() {

// 输入"顶点数"和"边数"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}

// 初始化"顶点"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = readChar();
}

// 1. 初始化"边"的权值
mMatrix = new int[vlen][vlen];
for (int i = 0; i < vlen; i++) {
for (int j = 0; j < vlen; j++) {
if (i==j)
mMatrix[i][j] = 0;
else
mMatrix[i][j] = INF;
}
}
// 2. 初始化"边"的权值: 根据用户的输入进行初始化
for (int i = 0; i < elen; i++) {
// 读取边的起始顶点,结束顶点,权值
System.out.printf("edge(%d):", i);
char c1 = readChar(); // 读取"起始顶点"
char c2 = readChar(); // 读取"结束顶点"
int weight = readInt(); // 读取"权值"

int p1 = getPosition(c1);
int p2 = getPosition(c2);
if (p1==-1 || p2==-1) {
System.out.printf("input error: invalid edge!\n");
return ;
}

mMatrix[p1][p2] = weight;
mMatrix[p2][p1] = weight;
}
}

/*
* 创建图(用已提供的矩阵)
*
* 参数说明:
* vexs -- 顶点数组
* matrix-- 矩阵(数据)
*/
public MatrixUDG(char[] vexs, int[][] matrix) {

// 初始化"顶点数"和"边数"
int vlen = vexs.length;

// 初始化"顶点"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++)
mVexs[i] = vexs[i];

// 初始化"边"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < vlen; i++)
for (int j = 0; j < vlen; j++)
mMatrix[i][j] = matrix[i][j];
}

/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i]==ch)
return i;
return -1;
}

/*
* 读取一个输入字符
*/
private char readChar() {
char ch='0';

do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')));

return ch;
}

/*
* 读取一个输入字符
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}

/*
* 返回顶点v的第一个邻接顶点的索引,失败则返回-1
*/
private int firstVertex(int v) {

if (v<0 || v>(mVexs.length-1))
return -1;

for (int i = 0; i < mVexs.length; i++)
if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
return i;

return -1;
}

/*
* 返回顶点v相对于w的下一个邻接顶点的索引,失败则返回-1
*/
private int nextVertex(int v, int w) {

if (v<0 || v>(mVexs.length-1) || w<0 || w>(mVexs.length-1))
return -1;

for (int i = w + 1; i < mVexs.length; i++)
if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
return i;

return -1;
}

/*
* 深度优先搜索遍历图的递归实现
*/
private void DFS(int i, boolean[] visited) {

visited[i] = true;
System.out.printf("%c ", mVexs[i]);
// 遍历该顶点的所有邻接顶点。若是没有访问过,那么继续往下走
for (int w = firstVertex(i); w >= 0; w = nextVertex(i, w)) {
if (!visited[w])
DFS(w, visited);
}
}

/*
* 深度优先搜索遍历图
*/
public void DFS() {
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

// 初始化所有顶点都没有被访问
for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("DFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i])
DFS(i, visited);
}
System.out.printf("\n");
}

/*
* 广度优先搜索(类似于树的层次遍历)
*/
public void BFS() {
int head = 0;
int rear = 0;
int[] queue = new int[mVexs.length]; // 辅组队列
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("BFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i]) {
visited[i] = true;
System.out.printf("%c ", mVexs[i]);
queue[rear++] = i; // 入队列
}

while (head != rear) {
int j = queue[head++]; // 出队列
for (int k = firstVertex(j); k >= 0; k = nextVertex(j, k)) { //k是为访问的邻接顶点
if (!visited[k]) {
visited[k] = true;
System.out.printf("%c ", mVexs[k]);
queue[rear++] = k;
}
}
}
}
System.out.printf("\n");
}

/*
* 打印矩阵队列图
*/
public void print() {
System.out.printf("Martix Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
for (int j = 0; j < mVexs.length; j++)
System.out.printf("%10d ", mMatrix[i][j]);
System.out.printf("\n");
}
}

/*
* prim最小生成树
*
* 参数说明:
* start -- 从图中的第start个元素开始,生成最小树
*/
public void prim(int start) {
int num = mVexs.length; // 顶点个数
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值

// prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
prims[index++] = mVexs[start];

// 初始化"顶点的权值数组",
// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (int i = 0; i < num; i++ )
weights[i] = mMatrix[start][i];
// 将第start个顶点的权值初始化为0。
// 可以理解为"第start个顶点到它自身的距离为0"。
weights[start] = 0;

for (int i = 0; i < num; i++) {
// 由于从start开始的,因此不需要再对第start个顶点进行处理。
if(start == i)
continue;

int j = 0;
int k = 0;
int min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}

// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k];
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && mMatrix[k][j] < weights[j])
weights[j] = mMatrix[k][j];
}
}

// 计算最小生成树的权值
int sum = 0;
for (int i = 1; i < index; i++) {
int min = INF;
// 获取prims[i]在mMatrix中的位置
int n = getPosition(prims[i]);
// 在vexs[0...i]中,找出到j的权值最小的顶点。
for (int j = 0; j < i; j++) {
int m = getPosition(prims[j]);
if (mMatrix[m][n]<min)
min = mMatrix[m][n];
}
sum += min;
}
// 打印最小生成树
System.out.printf("PRIM(%c)=%d: ", mVexs[start], sum);
for (int i = 0; i < index; i++)
System.out.printf("%c ", prims[i]);
System.out.printf("\n");
}

public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int matrix[][] = {
/*A*//*B*//*C*//*D*//*E*//*F*//*G*/
/*A*/ { 0, 12, INF, INF, INF, 16, 14},
/*B*/ { 12, 0, 10, INF, INF, 7, INF},
/*C*/ { INF, 10, 0, 3, 5, 6, INF},
/*D*/ { INF, INF, 3, 0, 4, INF, INF},
/*E*/ { INF, INF, 5, 4, 0, 2, 8},
/*F*/ { 16, 7, 6, INF, 2, 0, 9},
/*G*/ { 14, INF, INF, INF, 8, 9, 0}};
MatrixUDG pG;

// 自定义"图"(输入矩阵队列)
//pG = new MatrixUDG();
// 采用已有的"图"
pG = new MatrixUDG(vexs, matrix);

//pG.print(); // 打印图
//pG.DFS(); // 深度优先遍历
//pG.BFS(); // 广度优先遍历
pG.prim(0); // prim算法生成最小生成树
}
}
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
/**
* Java prim算法生成最小生成树(邻接表)
*
* @author skywang
* @date 2014/04/23
*/

import java.io.IOException;
import java.util.Scanner;

public class ListUDG {
private static int INF = Integer.MAX_VALUE;

// 邻接表中表对应的链表的顶点
private class ENode {
int ivex; // 该边所指向的顶点的位置
int weight; // 该边的权
ENode nextEdge; // 指向下一条弧的指针
}

// 邻接表中表的顶点
private class VNode {
char data; // 顶点信息
ENode firstEdge; // 指向第一条依附该顶点的弧
};

private VNode[] mVexs; // 顶点数组


/*
* 创建图(自己输入数据)
*/
public ListUDG() {

// 输入"顶点数"和"边数"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}

// 初始化"顶点"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = new VNode();
mVexs[i].data = readChar();
mVexs[i].firstEdge = null;
}

// 初始化"边"
//mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 读取边的起始顶点和结束顶点
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int weight = readInt();

int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
node1.weight = weight;
// 将node1链接到"p1所在链表的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
node2.weight = weight;
// 将node2链接到"p2所在链表的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge, node2);
}
}

/*
* 创建图(用已提供的矩阵)
*
* 参数说明:
* vexs -- 顶点数组
* edges -- 边
*/
public ListUDG(char[] vexs, EData[] edges) {

// 初始化"顶点数"和"边数"
int vlen = vexs.length;
int elen = edges.length;

// 初始化"顶点"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
mVexs[i] = new VNode();
mVexs[i].data = vexs[i];
mVexs[i].firstEdge = null;
}

// 初始化"边"
for (int i = 0; i < elen; i++) {
// 读取边的起始顶点和结束顶点
char c1 = edges[i].start;
char c2 = edges[i].end;
int weight = edges[i].weight;

// 读取边的起始顶点和结束顶点
int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
node1.weight = weight;
// 将node1链接到"p1所在链表的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
node2.weight = weight;
// 将node2链接到"p2所在链表的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge, node2);
}
}

/*
* 将node节点链接到list的最后
*/
private void linkLast(ENode list, ENode node) {
ENode p = list;

while(p.nextEdge!=null)
p = p.nextEdge;
p.nextEdge = node;
}

/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i].data==ch)
return i;
return -1;
}

/*
* 读取一个输入字符
*/
private char readChar() {
char ch='0';

do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')));

return ch;
}

/*
* 读取一个输入字符
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}

/*
* 深度优先搜索遍历图的递归实现
*/
private void DFS(int i, boolean[] visited) {
ENode node;

visited[i] = true;
System.out.printf("%c ", mVexs[i].data);
node = mVexs[i].firstEdge;
while (node != null) {
if (!visited[node.ivex])
DFS(node.ivex, visited);
node = node.nextEdge;
}
}

/*
* 深度优先搜索遍历图
*/
public void DFS() {
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

// 初始化所有顶点都没有被访问
for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("DFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i])
DFS(i, visited);
}
System.out.printf("\n");
}

/*
* 广度优先搜索(类似于树的层次遍历)
*/
public void BFS() {
int head = 0;
int rear = 0;
int[] queue = new int[mVexs.length]; // 辅组队列
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("BFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i]) {
visited[i] = true;
System.out.printf("%c ", mVexs[i].data);
queue[rear++] = i; // 入队列
}

while (head != rear) {
int j = queue[head++]; // 出队列
ENode node = mVexs[j].firstEdge;
while (node != null) {
int k = node.ivex;
if (!visited[k])
{
visited[k] = true;
System.out.printf("%c ", mVexs[k].data);
queue[rear++] = k;
}
node = node.nextEdge;
}
}
}
System.out.printf("\n");
}

/*
* 打印矩阵队列图
*/
public void print() {
System.out.printf("List Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("%d(%c): ", i, mVexs[i].data);
ENode node = mVexs[i].firstEdge;
while (node != null) {
System.out.printf("%d(%c) ", node.ivex, mVexs[node.ivex].data);
node = node.nextEdge;
}
System.out.printf("\n");
}
}

/*
* 获取边<start, end>的权值;若start和end不是连通的,则返回无穷大。
*/
private int getWeight(int start, int end) {

if (start==end)
return 0;

ENode node = mVexs[start].firstEdge;
while (node!=null) {
if (end==node.ivex)
return node.weight;
node = node.nextEdge;
}

return INF;
}

/*
* prim最小生成树
*
* 参数说明:
* start -- 从图中的第start个元素开始,生成最小树
*/
public void prim(int start) {
int min,i,j,k,m,n,tmp,sum;
int num = mVexs.length;
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值

// prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
prims[index++] = mVexs[start].data;

// 初始化"顶点的权值数组",
// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (i = 0; i < num; i++ )
weights[i] = getWeight(start, i);

for (i = 0; i < num; i++) {
// 由于从start开始的,因此不需要再对第start个顶点进行处理。
if(start == i)
continue;

j = 0;
k = 0;
min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}

// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k].data;
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 获取第k个顶点到第j个顶点的权值
tmp = getWeight(k, j);
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && tmp < weights[j])
weights[j] = tmp;
}
}

// 计算最小生成树的权值
sum = 0;
for (i = 1; i < index; i++) {
min = INF;
// 获取prims[i]在矩阵表中的位置
n = getPosition(prims[i]);
// 在vexs[0...i]中,找出到j的权值最小的顶点。
for (j = 0; j < i; j++) {
m = getPosition(prims[j]);
tmp = getWeight(m, n);
if (tmp < min)
min = tmp;
}
sum += min;
}
// 打印最小生成树
System.out.printf("PRIM(%c)=%d: ", mVexs[start].data, sum);
for (i = 0; i < index; i++)
System.out.printf("%c ", prims[i]);
System.out.printf("\n");
}


// 示例类:边的结构体(用来演示)
private static class EData {
char start; // 边的起点
char end; // 边的终点
int weight; // 边的权重

public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
};

public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
EData[] edges = {
// 起点 终点 权
new EData('A', 'B', 12),
new EData('A', 'F', 16),
new EData('A', 'G', 14),
new EData('B', 'C', 10),
new EData('B', 'F', 7),
new EData('C', 'D', 3),
new EData('C', 'E', 5),
new EData('C', 'F', 6),
new EData('D', 'E', 4),
new EData('E', 'F', 2),
new EData('E', 'G', 8),
new EData('F', 'G', 9),
};
ListUDG pG;

// 自定义"图"(输入矩阵队列)
//pG = new ListUDG();
// 采用已有的"图"
pG = new ListUDG(vexs, edges);

//pG.print(); // 打印图
//pG.DFS(); // 深度优先遍历
//pG.BFS(); // 广度优先遍历
pG.prim(0); // prim算法生成最小生成树
}
}

克鲁斯卡尔算法

无论是普里姆算法(Prim)还是克鲁斯卡尔算法(Kruskal),他们考虑问题的出发点都是为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能的小,普里姆算法是以『某顶点为起点』,逐步找各个顶点上最小权值的边来构建最小生成树的

但是现在我们换一种思考方式,我们从边出发,直接去找『最小权值的边』来构建生成树,这也是克鲁斯卡尔算法的精髓,还是老规矩,我们通过图片来进行了解,如下,我们使用红点来表示顶点

20210601143506

然后按照权值递增的顺序依次连接 (0, 2),(3, 5),(1, 4) 和 (2, 5),我们将其也标注为红色,如下

20210601143542

由于边 (0, 3) 的两个顶点在同一棵树上,所以舍去,而边 (2, 4) 和 (1, 2) 的长度相同,可以任选一条加入,最后结果如下

20210601143603

最后我们来看如何用代码进行实现,我们还是以上面的示例为例,先将图转换成一个边集数组,如下

20210601143625

但是这一次,我们需要借助于一个 parent 数组来进行实现,初始化如下

20210601143646

下面是完成后的 parent 数组的变化,如下

20210601143706

代码实现

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
/**
* Java: Kruskal算法生成最小生成树(邻接矩阵)
*
* @author skywang
* @date 2014/04/24
*/

import java.io.IOException;
import java.util.Scanner;

public class MatrixUDG {

private int mEdgNum; // 边的数量
private char[] mVexs; // 顶点集合
private int[][] mMatrix; // 邻接矩阵
private static final int INF = Integer.MAX_VALUE; // 最大值

/*
* 创建图(自己输入数据)
*/
public MatrixUDG() {

// 输入"顶点数"和"边数"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}

// 初始化"顶点"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = readChar();
}

// 1. 初始化"边"的权值
mEdgNum = elen;
mMatrix = new int[vlen][vlen];
for (int i = 0; i < vlen; i++) {
for (int j = 0; j < vlen; j++) {
if (i==j)
mMatrix[i][j] = 0;
else
mMatrix[i][j] = INF;
}
}
// 2. 初始化"边"的权值: 根据用户的输入进行初始化
for (int i = 0; i < elen; i++) {
// 读取边的起始顶点,结束顶点,权值
System.out.printf("edge(%d):", i);
char c1 = readChar(); // 读取"起始顶点"
char c2 = readChar(); // 读取"结束顶点"
int weight = readInt(); // 读取"权值"

int p1 = getPosition(c1);
int p2 = getPosition(c2);
if (p1==-1 || p2==-1) {
System.out.printf("input error: invalid edge!\n");
return ;
}

mMatrix[p1][p2] = weight;
mMatrix[p2][p1] = weight;
}
}

/*
* 创建图(用已提供的矩阵)
*
* 参数说明:
* vexs -- 顶点数组
* matrix-- 矩阵(数据)
*/
public MatrixUDG(char[] vexs, int[][] matrix) {

// 初始化"顶点数"和"边数"
int vlen = vexs.length;

// 初始化"顶点"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++)
mVexs[i] = vexs[i];

// 初始化"边"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < vlen; i++)
for (int j = 0; j < vlen; j++)
mMatrix[i][j] = matrix[i][j];

// 统计"边"
mEdgNum = 0;
for (int i = 0; i < vlen; i++)
for (int j = i+1; j < vlen; j++)
if (mMatrix[i][j]!=INF)
mEdgNum++;
}

/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i]==ch)
return i;
return -1;
}

/*
* 读取一个输入字符
*/
private char readChar() {
char ch='0';

do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')));

return ch;
}

/*
* 读取一个输入字符
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}

/*
* 返回顶点v的第一个邻接顶点的索引,失败则返回-1
*/
private int firstVertex(int v) {

if (v<0 || v>(mVexs.length-1))
return -1;

for (int i = 0; i < mVexs.length; i++)
if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
return i;

return -1;
}

/*
* 返回顶点v相对于w的下一个邻接顶点的索引,失败则返回-1
*/
private int nextVertex(int v, int w) {

if (v<0 || v>(mVexs.length-1) || w<0 || w>(mVexs.length-1))
return -1;

for (int i = w + 1; i < mVexs.length; i++)
if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
return i;

return -1;
}

/*
* 深度优先搜索遍历图的递归实现
*/
private void DFS(int i, boolean[] visited) {

visited[i] = true;
System.out.printf("%c ", mVexs[i]);
// 遍历该顶点的所有邻接顶点。若是没有访问过,那么继续往下走
for (int w = firstVertex(i); w >= 0; w = nextVertex(i, w)) {
if (!visited[w])
DFS(w, visited);
}
}

/*
* 深度优先搜索遍历图
*/
public void DFS() {
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

// 初始化所有顶点都没有被访问
for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("DFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i])
DFS(i, visited);
}
System.out.printf("\n");
}

/*
* 广度优先搜索(类似于树的层次遍历)
*/
public void BFS() {
int head = 0;
int rear = 0;
int[] queue = new int[mVexs.length]; // 辅组队列
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("BFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i]) {
visited[i] = true;
System.out.printf("%c ", mVexs[i]);
queue[rear++] = i; // 入队列
}

while (head != rear) {
int j = queue[head++]; // 出队列
for (int k = firstVertex(j); k >= 0; k = nextVertex(j, k)) { //k是为访问的邻接顶点
if (!visited[k]) {
visited[k] = true;
System.out.printf("%c ", mVexs[k]);
queue[rear++] = k;
}
}
}
}
System.out.printf("\n");
}

/*
* 打印矩阵队列图
*/
public void print() {
System.out.printf("Martix Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
for (int j = 0; j < mVexs.length; j++)
System.out.printf("%10d ", mMatrix[i][j]);
System.out.printf("\n");
}
}

/*
* prim最小生成树
*
* 参数说明:
* start -- 从图中的第start个元素开始,生成最小树
*/
public void prim(int start) {
int num = mVexs.length; // 顶点个数
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值

// prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
prims[index++] = mVexs[start];

// 初始化"顶点的权值数组",
// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (int i = 0; i < num; i++ )
weights[i] = mMatrix[start][i];
// 将第start个顶点的权值初始化为0。
// 可以理解为"第start个顶点到它自身的距离为0"。
weights[start] = 0;

for (int i = 0; i < num; i++) {
// 由于从start开始的,因此不需要再对第start个顶点进行处理。
if(start == i)
continue;

int j = 0;
int k = 0;
int min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}

// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k];
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && mMatrix[k][j] < weights[j])
weights[j] = mMatrix[k][j];
}
}

// 计算最小生成树的权值
int sum = 0;
for (int i = 1; i < index; i++) {
int min = INF;
// 获取prims[i]在mMatrix中的位置
int n = getPosition(prims[i]);
// 在vexs[0...i]中,找出到j的权值最小的顶点。
for (int j = 0; j < i; j++) {
int m = getPosition(prims[j]);
if (mMatrix[m][n]<min)
min = mMatrix[m][n];
}
sum += min;
}
// 打印最小生成树
System.out.printf("PRIM(%c)=%d: ", mVexs[start], sum);
for (int i = 0; i < index; i++)
System.out.printf("%c ", prims[i]);
System.out.printf("\n");
}

/*
* 克鲁斯卡尔(Kruskal)最小生成树
*/
public void kruskal() {
int index = 0; // rets数组的索引
int[] vends = new int[mEdgNum]; // 用于保存"已有最小生成树"中每个顶点在该最小树中的终点。
EData[] rets = new EData[mEdgNum]; // 结果数组,保存kruskal最小生成树的边
EData[] edges; // 图对应的所有边

// 获取"图中所有的边"
edges = getEdges();
// 将边按照"权"的大小进行排序(从小到大)
sortEdges(edges, mEdgNum);

for (int i=0; i<mEdgNum; i++) {
int p1 = getPosition(edges[i].start); // 获取第i条边的"起点"的序号
int p2 = getPosition(edges[i].end); // 获取第i条边的"终点"的序号

int m = getEnd(vends, p1); // 获取p1在"已有的最小生成树"中的终点
int n = getEnd(vends, p2); // 获取p2在"已有的最小生成树"中的终点
// 如果m!=n,意味着"边i"与"已经添加到最小生成树中的顶点"没有形成环路
if (m != n) {
vends[m] = n; // 设置m在"已有的最小生成树"中的终点为n
rets[index++] = edges[i]; // 保存结果
}
}

// 统计并打印"kruskal最小生成树"的信息
int length = 0;
for (int i = 0; i < index; i++)
length += rets[i].weight;
System.out.printf("Kruskal=%d: ", length);
for (int i = 0; i < index; i++)
System.out.printf("(%c,%c) ", rets[i].start, rets[i].end);
System.out.printf("\n");
}

/*
* 获取图中的边
*/
private EData[] getEdges() {
int index=0;
EData[] edges;

edges = new EData[mEdgNum];
for (int i=0; i < mVexs.length; i++) {
for (int j=i+1; j < mVexs.length; j++) {
if (mMatrix[i][j]!=INF) {
edges[index++] = new EData(mVexs[i], mVexs[j], mMatrix[i][j]);
}
}
}

return edges;
}

/*
* 对边按照权值大小进行排序(由小到大)
*/
private void sortEdges(EData[] edges, int elen) {

for (int i=0; i<elen; i++) {
for (int j=i+1; j<elen; j++) {

if (edges[i].weight > edges[j].weight) {
// 交换"边i"和"边j"
EData tmp = edges[i];
edges[i] = edges[j];
edges[j] = tmp;
}
}
}
}

/*
* 获取i的终点
*/
private int getEnd(int[] vends, int i) {
while (vends[i] != 0)
i = vends[i];
return i;
}

// 边的结构体
private static class EData {
char start; // 边的起点
char end; // 边的终点
int weight; // 边的权重

public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
};


public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int matrix[][] = {
/*A*//*B*//*C*//*D*//*E*//*F*//*G*/
/*A*/ { 0, 12, INF, INF, INF, 16, 14},
/*B*/ { 12, 0, 10, INF, INF, 7, INF},
/*C*/ { INF, 10, 0, 3, 5, 6, INF},
/*D*/ { INF, INF, 3, 0, 4, INF, INF},
/*E*/ { INF, INF, 5, 4, 0, 2, 8},
/*F*/ { 16, 7, 6, INF, 2, 0, 9},
/*G*/ { 14, INF, INF, INF, 8, 9, 0}};
MatrixUDG pG;

// 自定义"图"(输入矩阵队列)
//pG = new MatrixUDG();
// 采用已有的"图"
pG = new MatrixUDG(vexs, matrix);

//pG.print(); // 打印图
//pG.DFS(); // 深度优先遍历
//pG.BFS(); // 广度优先遍历
//pG.prim(0); // prim算法生成最小生成树
pG.kruskal(); // Kruskal算法生成最小生成树
}
}
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
/**
* Java prim算法生成最小生成树(邻接表)
*
* @author skywang
* @date 2014/04/23
*/

import java.io.IOException;
import java.util.Scanner;

public class ListUDG {
private static int INF = Integer.MAX_VALUE;

// 邻接表中表对应的链表的顶点
private class ENode {
int ivex; // 该边所指向的顶点的位置
int weight; // 该边的权
ENode nextEdge; // 指向下一条弧的指针
}

// 邻接表中表的顶点
private class VNode {
char data; // 顶点信息
ENode firstEdge; // 指向第一条依附该顶点的弧
};

private int mEdgNum; // 边的数量
private VNode[] mVexs; // 顶点数组


/*
* 创建图(自己输入数据)
*/
public ListUDG() {

// 输入"顶点数"和"边数"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}

// 初始化"顶点"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = new VNode();
mVexs[i].data = readChar();
mVexs[i].firstEdge = null;
}

// 初始化"边"
mEdgNum = elen;
for (int i = 0; i < elen; i++) {
// 读取边的起始顶点和结束顶点
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int weight = readInt();

int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
node1.weight = weight;
// 将node1链接到"p1所在链表的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
node2.weight = weight;
// 将node2链接到"p2所在链表的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge, node2);
}
}

/*
* 创建图(用已提供的矩阵)
*
* 参数说明:
* vexs -- 顶点数组
* edges -- 边
*/
public ListUDG(char[] vexs, EData[] edges) {

// 初始化"顶点数"和"边数"
int vlen = vexs.length;
int elen = edges.length;

// 初始化"顶点"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
mVexs[i] = new VNode();
mVexs[i].data = vexs[i];
mVexs[i].firstEdge = null;
}

// 初始化"边"
mEdgNum = elen;
for (int i = 0; i < elen; i++) {
// 读取边的起始顶点和结束顶点
char c1 = edges[i].start;
char c2 = edges[i].end;
int weight = edges[i].weight;

// 读取边的起始顶点和结束顶点
int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
node1.weight = weight;
// 将node1链接到"p1所在链表的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
node2.weight = weight;
// 将node2链接到"p2所在链表的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge, node2);
}
}

/*
* 将node节点链接到list的最后
*/
private void linkLast(ENode list, ENode node) {
ENode p = list;

while(p.nextEdge!=null)
p = p.nextEdge;
p.nextEdge = node;
}

/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i].data==ch)
return i;
return -1;
}

/*
* 读取一个输入字符
*/
private char readChar() {
char ch='0';

do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')));

return ch;
}

/*
* 读取一个输入字符
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}

/*
* 深度优先搜索遍历图的递归实现
*/
private void DFS(int i, boolean[] visited) {
ENode node;

visited[i] = true;
System.out.printf("%c ", mVexs[i].data);
node = mVexs[i].firstEdge;
while (node != null) {
if (!visited[node.ivex])
DFS(node.ivex, visited);
node = node.nextEdge;
}
}

/*
* 深度优先搜索遍历图
*/
public void DFS() {
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

// 初始化所有顶点都没有被访问
for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("DFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i])
DFS(i, visited);
}
System.out.printf("\n");
}

/*
* 广度优先搜索(类似于树的层次遍历)
*/
public void BFS() {
int head = 0;
int rear = 0;
int[] queue = new int[mVexs.length]; // 辅组队列
boolean[] visited = new boolean[mVexs.length]; // 顶点访问标记

for (int i = 0; i < mVexs.length; i++)
visited[i] = false;

System.out.printf("BFS: ");
for (int i = 0; i < mVexs.length; i++) {
if (!visited[i]) {
visited[i] = true;
System.out.printf("%c ", mVexs[i].data);
queue[rear++] = i; // 入队列
}

while (head != rear) {
int j = queue[head++]; // 出队列
ENode node = mVexs[j].firstEdge;
while (node != null) {
int k = node.ivex;
if (!visited[k])
{
visited[k] = true;
System.out.printf("%c ", mVexs[k].data);
queue[rear++] = k;
}
node = node.nextEdge;
}
}
}
System.out.printf("\n");
}

/*
* 打印矩阵队列图
*/
public void print() {
System.out.printf("List Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("%d(%c): ", i, mVexs[i].data);
ENode node = mVexs[i].firstEdge;
while (node != null) {
System.out.printf("%d(%c) ", node.ivex, mVexs[node.ivex].data);
node = node.nextEdge;
}
System.out.printf("\n");
}
}

/*
* 获取边<start, end>的权值;若start和end不是连通的,则返回无穷大。
*/
private int getWeight(int start, int end) {

if (start==end)
return 0;

ENode node = mVexs[start].firstEdge;
while (node!=null) {
if (end==node.ivex)
return node.weight;
node = node.nextEdge;
}

return INF;
}

/*
* prim最小生成树
*
* 参数说明:
* start -- 从图中的第start个元素开始,生成最小树
*/
public void prim(int start) {
int min,i,j,k,m,n,tmp,sum;
int num = mVexs.length;
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值

// prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
prims[index++] = mVexs[start].data;

// 初始化"顶点的权值数组",
// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (i = 0; i < num; i++ )
weights[i] = getWeight(start, i);

for (i = 0; i < num; i++) {
// 由于从start开始的,因此不需要再对第start个顶点进行处理。
if(start == i)
continue;

j = 0;
k = 0;
min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}

// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k].data;
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 获取第k个顶点到第j个顶点的权值
tmp = getWeight(k, j);
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && tmp < weights[j])
weights[j] = tmp;
}
}

// 计算最小生成树的权值
sum = 0;
for (i = 1; i < index; i++) {
min = INF;
// 获取prims[i]在矩阵表中的位置
n = getPosition(prims[i]);
// 在vexs[0...i]中,找出到j的权值最小的顶点。
for (j = 0; j < i; j++) {
m = getPosition(prims[j]);
tmp = getWeight(m, n);
if (tmp < min)
min = tmp;
}
sum += min;
}
// 打印最小生成树
System.out.printf("PRIM(%c)=%d: ", mVexs[start].data, sum);
for (i = 0; i < index; i++)
System.out.printf("%c ", prims[i]);
System.out.printf("\n");
}

/*
* 克鲁斯卡尔(Kruskal)最小生成树
*/
public void kruskal() {
int index = 0; // rets数组的索引
int[] vends = new int[mEdgNum]; // 用于保存"已有最小生成树"中每个顶点在该最小树中的终点。
EData[] rets = new EData[mEdgNum]; // 结果数组,保存kruskal最小生成树的边
EData[] edges; // 图对应的所有边

// 获取"图中所有的边"
edges = getEdges();
// 将边按照"权"的大小进行排序(从小到大)
sortEdges(edges, mEdgNum);

for (int i=0; i<mEdgNum; i++) {
int p1 = getPosition(edges[i].start); // 获取第i条边的"起点"的序号
int p2 = getPosition(edges[i].end); // 获取第i条边的"终点"的序号

int m = getEnd(vends, p1); // 获取p1在"已有的最小生成树"中的终点
int n = getEnd(vends, p2); // 获取p2在"已有的最小生成树"中的终点
// 如果m!=n,意味着"边i"与"已经添加到最小生成树中的顶点"没有形成环路
if (m != n) {
vends[m] = n; // 设置m在"已有的最小生成树"中的终点为n
rets[index++] = edges[i]; // 保存结果
}
}

// 统计并打印"kruskal最小生成树"的信息
int length = 0;
for (int i = 0; i < index; i++)
length += rets[i].weight;
System.out.printf("Kruskal=%d: ", length);
for (int i = 0; i < index; i++)
System.out.printf("(%c,%c) ", rets[i].start, rets[i].end);
System.out.printf("\n");
}

/*
* 获取图中的边
*/
private EData[] getEdges() {
int index=0;
EData[] edges;

edges = new EData[mEdgNum];
for (int i=0; i < mVexs.length; i++) {

ENode node = mVexs[i].firstEdge;
while (node != null) {
if (node.ivex > i) {
edges[index++] = new EData(mVexs[i].data, mVexs[node.ivex].data, node.weight);
}
node = node.nextEdge;
}
}

return edges;
}

/*
* 对边按照权值大小进行排序(由小到大)
*/
private void sortEdges(EData[] edges, int elen) {

for (int i=0; i<elen; i++) {
for (int j=i+1; j<elen; j++) {

if (edges[i].weight > edges[j].weight) {
// 交换"边i"和"边j"
EData tmp = edges[i];
edges[i] = edges[j];
edges[j] = tmp;
}
}
}
}

/*
* 获取i的终点
*/
private int getEnd(int[] vends, int i) {
while (vends[i] != 0)
i = vends[i];
return i;
}

// 边的结构体
private static class EData {
char start; // 边的起点
char end; // 边的终点
int weight; // 边的权重

public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
};

public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
EData[] edges = {
// 起点 终点 权
new EData('A', 'B', 12),
new EData('A', 'F', 16),
new EData('A', 'G', 14),
new EData('B', 'C', 10),
new EData('B', 'F', 7),
new EData('C', 'D', 3),
new EData('C', 'E', 5),
new EData('C', 'F', 6),
new EData('D', 'E', 4),
new EData('E', 'F', 2),
new EData('E', 'G', 8),
new EData('F', 'G', 9),
};
ListUDG pG;

// 自定义"图"(输入矩阵队列)
//pG = new ListUDG();
// 采用已有的"图"
pG = new ListUDG(vexs, edges);

//pG.print(); // 打印图
//pG.DFS(); // 深度优先遍历
//pG.BFS(); // 广度优先遍历
//pG.prim(0); // prim算法生成最小生成树

pG.kruskal(); // Kruskal算法生成最小生成树
}
}

参考

普里姆算法和克鲁斯卡尔算法

Prim算法(三)之 Java详解

Kruskal算法(三)之 Java详解

评论