【图论】【最小生成树】Prim和Kruskal算法

时间:2023-02-07 11:41:06

在数据结构的教材上,讲到的图的最小生成树算法有两种,一种是Prim(普利姆)算法,一种是Kruskal(克鲁斯卡尔)算法。

两种算法的生成思路有所不同:

Prim算法

算法思想:

  算法思想就是每次找到一个距离生成集合最近的点,加入,然后更新距离剩余点之间的距离,继续迭代。

算法步骤:

1.任意选择一个点作为起点,将该点标记为已加入最小生成树,使用一个数组表示该点已加入,join[i] = 1表示i点已加入集合;

2.将最小生成树集合作为整体,计算到其他未加入该集合的点(jion[i] == 0的点)之间的距离,使用dis[i]表示,dis[i] 表示集合距离点i之间的距离;

3.然后找出dis[i] 中距离最小的,那么 i 就是下一个要加入集合的点

4. 将join[i]赋值为1,表示已加入

5. 重新计算dis[i]。因为有新的点加入,判断该点的加入是否使集合距离其他未加入的点之间的距离变小,如果有变小,则更新dis[i]。

6.继续执行步骤3,直到所有的点都加入join数组(其实是查找n-1次后自动结束,因为指定的起始点,所有只需要再查找n-1次,即for循环从2到n即可)

输出路径:可以通过记录每个节点的前置节点的方式记录路径。加入新节点后,导致dis[j] 距离变小,那么新加入的点就是j的前置节点。

 

Kruskal算法

算法思想:

  首先将所有的边按照权重从小到大排序,然后依次取出一条边,尝试将组成集合。如果两个点已经在同一个集合,说明已经添加了,则跳过;如果尚未加入集合,这组成一个新的集合;如果是在不同的集合,则将集合合并,形成一个新的集合,直到取出n-1条有效的边或者所有点都在一个集合中,则表示已生成。

1.使用排序算法将边按权重从小到大排序

2.初始时,点均为构成集合(或者说自成集合,这里理解为未成集合),使用join数组表示集合,初始是join[i]均为0.

3.取出一条边,判断两个点 i,j

  1)如果group[i],group[j] 都为0,则说明都还没有加入集合,那么设置group[i] = group[j] = count (count用来表示加入的边数,也能够用来区分集合)

  2)如果group[i],group[j] 不相等,说明两个点不在同一个集合。

    如果i,j有一个的join值为0,说明未加入,那么只需设置这个点的join值也已加入的那个点一致。

    如果两个点都已加入集合,但是处在不同结合,那么此时就需要合并集合。(变量group,把两个集合的点设置为同样的值)

4.所有点在group里对应的值一致,表示形式了最小生成树集合。

 

 
  3 import com.alibaba.fastjson.JSON;
  4 import org.apache.commons.lang3.StringUtils;
  5 import org.junit.Test;
  6 
  7 import java.util.Arrays;
  8 
  9 public class MinimumTree {
 10     /**
 11      * 最小生成树
 12      */
 13     int n;
 14     int e[][];
 15     int dis[];
 16 
 17     public void prim() {
 18         int pos = 1; // 从1号节点开始,可以任意指定;
 19         int join[] = new int[n + 1]; // 记录各个点加入的情况
 20         int weight = 0;
 21         // 初始化最小生成树集合与其他各个点的距离
 22         for (int i = 1; i <= n; i++) {
 23             dis[i] = i == pos ? 0 : e[pos][i];
 24         }
 25 
 26         for (int i = 2; i <= n; i++) { // 从寻找第二个点开始(并不是代表要把2号点添加进去)
 27             int min = Integer.MAX_VALUE;
 28             for (int j = 1; j <= n; j++) {
 29                 if (join[j] == 0 && dis[j] != 0 && min > dis[j]) { // 找出集合距离未加入的点的最近边及点; dis[j]!=0 表示跳过和自己比较
 30                     min = dis[j];
 31                     pos = j; // 找到最小位置,下一个要加入的点就是这个点
 32                 }
 33             }
 34 
 35             join[pos] = 1; // 标记为以加入最小生成树集合
 36             weight += min;
 37             System.out.println(String.format("pos: %d - min: %d", pos, min));
 38 
 39             //重新计算集合距离其他点的距离
 40             for (int j = 1; j <= n; j++) {
 41                 if (join[j] == 0 && dis[j] > e[pos][j]) { // 比较新加入的点是否带来的更短的距离,
 42                     dis[j] = e[pos][j]; //有,则更新。保证dis里存储的是到其他点的最近距离
 43                 }
 44             }
 45         }
 46         System.out.println("weight:" + weight);
 47     }
 48 
 49     public void kruskal(int edgeNum) {
 50         Edge[] edges = new Edge[edgeNum];
 51         int weight = 0;
 52         int pos = 0;
 53         for (int i = 1; i <= n; i++) {
 54             for (int j = 1; j < i; j++) {
 55                 if (e[i][j] < Integer.MAX_VALUE) {
 56                     Edge edge = new Edge();
 57                     edge.x = i;
 58                     edge.y = j;
 59                     edge.v = e[i][j];
 60                     edges[pos++] = edge;
 61                 }
 62             }
 63         }
 64         sort(edges); // 将边按照权重排序
 65         int group[] = new int[n + 1];
 66         int count = 0; // 记录添加的边数,当边数=n-1则停止
 67         for (int i = 0; i < n; i++) {
 68             Edge edge = edges[i];
 69             int x = edge.x;
 70             int y = edge.y;
 71             if (count != n - 1) {
 72                 if (group[x] != 0 && group[y] == group[x]) {
 73                     continue;
 74                 }
 75 
 76                 if (group[x] == 0 && group[y] == 0) {
 77                     group[x] = group[y] = count + 1;
 78                 } else if (group[x] != group[y]) { // 不相等分两种情况:1,两个点在不同的集合,2,一个点在集合,另一个的尚未加入集合
 79 
 80                     if (group[x] > 0 && group[y] > 0) {// 两个点在不同的集合
 81                         int tmp = group[x];
 82                         for (int g = 1; g <= n; g++) {
 83                             if (group[g] == tmp) {
 84                                 group[g] = group[y]; //把x所在的集合和y所在结合合并
 85                             }
 86                         }
 87                     } else { // 有一个点为加入集合的情况
 88                         if (group[x] != 0) {
 89                             group[y] = group[x];
 90                         } else {
 91                             group[x] = group[y];
 92                         }
 93                     }
 94                 }
 95                 weight += edge.v;
 96                 count++;
 97                 System.out.println(String.format("edge: %s->%s: %s", x, y, edge.v));
 98             }
 99             if (count == n - 1) { // 所有点
100                 break;
101             }
102         }
103         System.out.println("weight:" + weight);
104     }
105 
106     public void sort(Edge[] edges) {
107         for (int i = 0; i < edges.length; i++) {
108             for (int j = 1; j < edges.length - i; j++) {
109                 if (edges[j - 1].v > edges[j].v) {
110                     Edge tmp = edges[j - 1];
111                     edges[j - 1] = edges[j];
112                     edges[j] = tmp;
113                 }
114             }
115         }
116         System.out.println(Arrays.asList(edges));
117     }
118 
119 
120     @Test
121     public void test01() {
122         n = 5;
123         e = new int[n + 1][n + 1];
124         dis = new int[n + 1];
125 
126         for (int i = 1; i <= n; i++) {
127             for (int j = 1; j <= n; j++) {
128                 if (i == j) {
129                     e[i][j] = 0;
130                 } else {
131                     e[i][j] = Integer.MAX_VALUE;
132                 }
133             }
134         }
135 
136         e[1][2] = e[2][1] = 2;
137         e[1][3] = e[3][1] = 4;
138         e[1][4] = e[4][1] = 7;
139 
140         e[2][3] = e[3][2] = 1;
141         e[2][5] = e[5][2] = 2;
142 
143         e[3][4] = e[4][3] = 1;
144         e[3][5] = e[5][3] = 6;
145         System.out.println("-----Prim----");
146         prim();
147         System.out.println("-----Kruskal----");
148         kruskal(7);
149 
150     }
151 
152     @Test
153     public void test02() {
154         n = 5;
155         e = new int[n + 1][n + 1];
156         dis = new int[n + 1];
157 
158         for (int i = 1; i <= n; i++) {
159             for (int j = 1; j <= n; j++) {
160                 if (i == j) {
161                     e[i][j] = 0;
162                 } else {
163                     e[i][j] = Integer.MAX_VALUE;
164                 }
165             }
166         }
167 
168         e[1][2] = e[2][1] = 2;
169         e[1][3] = e[3][1] = 10;
170         e[1][5] = e[5][1] = 12;
171 
172         e[2][5] = e[5][2] = 8;
173         e[2][4] = e[4][2] = 9;
174 
175         e[3][4] = e[4][3] = 7;
176         e[3][5] = e[5][3] = 6;
177 
178         e[4][5] = e[5][4] = 3;
179 
180         System.out.println("-----Prim----");
181         prim();
182         System.out.println("-----Kruskal----");
183         kruskal(8);
184     }
185 }
186 
187 class Edge {
188     public int x;
189     public int y;
190     public int v;
191 
192     @Override
193     public String toString() {
194         return v + "";
195     }
196 }