【图论】【最小生成树】Prim和Kruskal算法
在数据结构的教材上,讲到的图的最小生成树算法有两种,一种是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 }