最小生成树——Prim算法实现最小生成树

什么是最小生成树?

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。

 

MST性质:

假设 N = (V, {E})是一个连通网,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值(代价)的边,其中 u ∈ U, v ∈ V - U,则必存在一棵包含边(u, v)的最小生成树。

证明如下(反证法):

  假设连通网N的任何一棵最小生成树中都不包含边(u,v)。

  设T是N的一棵最小生成树,则由上述假设可知,T中不包含(u,v)。由于T是连通的,因此有一条从u到v的路径。将T中的顶点集分为两部分:顶点集U和顶点集V - U,其中,顶点集U和相关的边构成一棵最小生成树T1,顶点集V - U和相关的边也构成一棵最小生成树T2,并且T1与T2之间只能有一条边,不妨设为(u`, v`),则u 和 u`之间、v和v`之间均是连通的。当把边(u,v)加入最小生成树T时,T中必有一条包含边(u,v)的回路,删除边(u`, v`),上述回路即被删除,由此得到另一棵生成树T`,如图所示。因为边(u,v)的权值小于等于(u`,v`)的权值,则T`的代价也就小于等于T的代价,因此T`也是N的最小生成树,且包含边(u,v),与假设矛盾,得证。

 

 什么是Prim算法?

普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

 

Prrim算法的基本思想是:假设N = (V, E)是连通网,令T = (U, TE)是N的最小生成树。算法的基本思想是:T的初始状态为U = {Vo} (Vo∈V),TE = {},重复执行下述操作:

① 在所有u∈U,v∈V - U的边(u,v)∈ E中,找一条代价最小的边(ui, vj)并入集合TE,同时将 vj 并入 U 中;

② 如此不断重复,知道 U = V 为止。此时 TE 必有 n - 1 条边,则 T(V,TE)为 N 的最小生成树。

 

连通图:

 

最小生成树:

Prim算法代码实现:

 1 public class MST {
 2 
 3     /**
 4      * 这里用邻接矩阵来存储图
 5      * 顶点编号从 A 开始,总共有7个顶点
 6      * 这里需要声明一下,如果两个顶点没有边直接连接,那么就设置这连个顶点的权值(也可认为是距离,这里具体问题具体分析)无穷大
 7      */
 8     private static int[][] closeMatrix = {
 9             {0, 50, 60, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE},
10             {50, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, 40, Integer.MAX_VALUE, Integer.MAX_VALUE},
11             {60, Integer.MAX_VALUE, 0, 52, Integer.MAX_VALUE, Integer.MAX_VALUE, 45},
12             {Integer.MAX_VALUE, Integer.MAX_VALUE, 52, 0, 50, 30, 42},
13             {Integer.MAX_VALUE, 40, Integer.MAX_VALUE, 50, 0, 70, Integer.MAX_VALUE},
14             {Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 30, 70, 0, Integer.MAX_VALUE},
15             {Integer.MAX_VALUE, Integer.MAX_VALUE, 45, 42, Integer.MAX_VALUE, Integer.MAX_VALUE, 0},
16     };
17 
18     private static char[] vex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
19 
20     /**
21      * @param closeMatrix
22      * @param startVex 这里开始的顶点用数字表示,A-0 B-1 C-2 D-3 E-4 F-5 G-6
23      */
24     public static void prim(int[][] closeMatrix, int startVex) {
25         // 两个辅助数组,因为随着点加入U中,可能原先是无穷大,后面直接变成最小都有可能,所以下面的两个数组的值都是在不停的变化
26         // lowCosts数组还有一个作用,如果lowCosts[i] == 0 表示编号为i的顶点已经加入到U集合中了,反之则没有加入U集合
27         // 这里是为想不改变原矩阵,所以才开了lowCosts作为辅助数组
28         int[] lowCosts = new int[vex.length];
29         // closest数组是用来存储当前加入U集合中的顶点编号,作用:为了后面打印边时,能知道边左边的顶点编号
30         int[] closest = new int[vex.length];
31         // 一开始加入第一个顶点时,初始化以上两个数组
32         // 加入一个顶点时没有边,所以不需要打印
33         for (int i = 0; i < vex.length; i++) {
34             lowCosts[i] = closeMatrix[startVex][i];
35             closest[i] = startVex;
36         }
37 
38         // k用于存储当前离U集合最近的顶点编号
39         int k = startVex;
40         // 开始加入V - U集合中的顶点,循环n - 1遍,该循环的目的:找出n - 1个顶点
41         for (int i = 1; i < vex.length; i++) {
42             // 找出离最小权值的边
43             int min = Integer.MAX_VALUE;
44             // 在V - U集合中找出离U最近的顶点k
45             for (int j = 0; j < vex.length; j++) {
46                 if (lowCosts[j] != 0 && lowCosts[j] < min) {
47                     // 更新
48                     min = lowCosts[j];
49                     k = j;
50                 }
51             }
52             // 程序运行到这,表示此时已经找到了我们想要找到的离U集合最近的顶点了,也可以将这里最近理解为代价最小或权值最小
53             // 打印边
54             System.out.printf("边(%c, %c)权为:%d\n", vex[closest[k]], vex[k], min);
55             // 不要忘了,标志编号为k的顶点已经加入U集合
56             lowCosts[k] = 0;
57             // 修改lowCosts 和 closest 数组
58             // 修改仍在V - U集合中的顶点,如果已经在U集合中的顶点不需要修改
59             for (int j = 0; j < vex.length; j++) {
60                 // 现在要比的是编号为k的顶点,所有顶点以编号为k的顶点为参考点,如果此时到它的权值比之前的到上一个顶点时的更优,则更新
61                 if (lowCosts[j] != 0 && closeMatrix[k][j] < lowCosts[j]) {
62                     // 此时 lowCosts[j] 的值不再是一开始到开始第一加入的顶点时的权值了,而是到编号为k的顶点的权值
63                     // 因为此时已经将k加入U集合,所有在 V - U集合中的顶点选取最小权值时都要以编号为k的顶点作为基点展开
64                     lowCosts[j] = closeMatrix[k][j];
65                     // 更新k,这一行至关重要
66                     closest[j] = k;
67                 }
68             }
69         }
70     }
71 
72     public static void main(String[] args) {
73         prim(closeMatrix, 0);
74     }
75 }

运行结果:

posted @ 2021-04-05 16:41  没有你哪有我  阅读(468)  评论(0编辑  收藏  举报