当前位置:   article > 正文

(Java)数据结构——图(第五节)Kruskal的实现最小生成树(MST)

(Java)数据结构——图(第五节)Kruskal的实现最小生成树(MST)

前言

本博客是博主用于复习数据结构以及算法的博客,如果疏忽出现错误,还望各位指正。

Kruskal算法(Kruskal的实现原理)

Kruskal算法的原理:

就是每次取最小的边,看看是不是与已经选择的构成回路,不构成回路,就加进来,继续,直到选了N-1条边。

对于判断回路来说,BFS和DFS应该是都可以的(类似于判断连通分量),但是,之前已经写过BFS和DFS代码了,所以在这里用并查集实现(当然,博主自己没试过DFS和BFS,感兴趣的可以自己去试一试)。

还是这个图,底下的是使用排序排好序的EData类。

Kruskal 的实现代码

首先,我们可以把排序的代码单拎出来

  1. //用于对之前定义的to进行排序
  2. public void toSort(){
  3. Collections.sort(this.to, new Comparator<EData>() {
  4. @Override
  5. public int compare(EData o1, EData o2) {
  6. //顺序对就是升序,顺序不对就是降序,比如我现在是o1和o2传进来,比较时候o1在前就是升序,o1在后就是降序
  7. int result = Integer.compare(o1.weight,o2.weight);
  8. return result;
  9. }
  10. });
  11. }

之后因为要用到并查集,我们把并查集的基本代码拎出来

  1. //并查集查找
  2. public int find(int x,int[] f){
  3. while(x!=f[x]){
  4. x = f[x];
  5. }
  6. return x;
  7. }
  8. //并查集合并
  9. public int union(int x,int y,int[] f){
  10. find(x,f);
  11. find(y,f);
  12. if(x!=y){
  13. f[x] = y;
  14. return y;
  15. }
  16. return -1; //如果一样的集合就返回-1
  17. }

之后就来首先Kruskal,注意最后返回的是一个ArrayList,为了方便看选的哪条边,我就直接把两头都加进去了,有需要的话,自行更改代码即可

  1. public ArrayList<Integer> kruskal(){
  2. //kruskal是对form to weight这种结构的类进行排序,然后选取最短的一条边,看是否形成回路,加入
  3. toSort(); //调用toSort进行排序
  4. //由于要判断是否形成回路,我们在这用并查集
  5. //初始化并查集
  6. int[] f = new int[vertexList.size()];
  7. for(int i = 0;i<vertexList.size();i++){
  8. f[i] = i;
  9. }
  10. ArrayList<Integer> res = new ArrayList<>();
  11. int count = 0;
  12. for(int i = 0;count != vertexList.size()-1 && i<this.to.size();i++){
  13. //之后就是开始取边了
  14. EData temp = this.to.get(i);
  15. if(union(temp.start,temp.end,f)!=-1){//如果查到不是一个集,就可以添加
  16. //这里都加进来是方便看哪个边
  17. res.add(temp.start);
  18. res.add(temp.end);
  19. count++;
  20. }
  21. }
  22. return res; //最后把集合返回去就行
  23. }

以上就是Kruskal算法实现最小生成树,这个不需要指定点生成,会排序选择最小边,而Prim指定某个点开始生成。记住哈,最小生成树可能不唯一!

以下是完整的测试代码

  1. //package GraphTest.Demo;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.LinkedList;
  5. public class Graph{//这是一个图类
  6. /***基础属性***/
  7. int[][] edges; //邻接矩阵存储边
  8. ArrayList<EData> to = new ArrayList<>(); //EData包含start,end,weight三个属性,相当于另一种存储方式,主要是为了实现kruskal算法定义的
  9. ArrayList<String> vertexList = new ArrayList<>(); //存储结点名称,当然你若想用Integer也可以,这个是我自己复习用的
  10. int numOfEdges; //边的个数
  11. boolean[] isVisited;
  12. //构造器
  13. Graph(int n){
  14. this.edges = new int[n][n];
  15. //为了方便,决定讲结点初始化为INF,这也方便后续某些操作
  16. int INF = Integer.MAX_VALUE;
  17. for(int i=0;i<n;i++){
  18. Arrays.fill(edges[i],INF);
  19. }
  20. this.numOfEdges = 0;
  21. this.isVisited = new boolean[n];
  22. }
  23. //插入点
  24. public void insertVertex(String vertex){//看自己个人喜好,我这边是一个一个在主方法里插入点的名称
  25. vertexList.add(vertex);
  26. }
  27. //点的个数
  28. public int getNumOfVertex(){
  29. return vertexList.size();
  30. }
  31. //获取第i个节点的名称
  32. public String getVertexByIndex(int i){
  33. return vertexList.get(i);
  34. }
  35. //获取该节点的下标
  36. public int getIndexOfVertex(String vertex){
  37. return vertexList.indexOf(vertex);
  38. }
  39. //插入边
  40. public void insertEdge(int v1,int v2,int weight){
  41. //注意,这里是无向图
  42. edges[v1][v2] = weight;
  43. edges[v2][v1] = weight;
  44. this.numOfEdges++;
  45. //如果要用Kruskal算法的话这里+
  46. to.add(new EData(v1,v2,weight)); //加入from to这种存储方式
  47. }
  48. //边的个数
  49. public int getNumOfEdge(){
  50. return this.numOfEdges;
  51. }
  52. //得到点到点的权值
  53. public int getWeight(int v1,int v2){//获取v1和v2边的权重
  54. return edges[v1][v2];
  55. }
  56. //打印图
  57. public void showGraph(){
  58. for(int[] line:edges){
  59. System.out.println(Arrays.toString(line));
  60. }
  61. }
  62. //获取index行 第一个邻接结点
  63. public int getFirstNeighbor(int index){
  64. for(int i = 0;i < vertexList.size();i++){
  65. if(edges[index][i] != Integer.MAX_VALUE){
  66. return i; //找到就返回邻接结点的坐标
  67. }
  68. }
  69. return -1; //没找到的话,返回-1
  70. }
  71. //获取row行 column列之后的第一个邻接结点
  72. public int getNextNeighbor(int row,int column){
  73. for(int i = column + 1;i < vertexList.size();i++){
  74. if(edges[row][i] != Integer.MAX_VALUE){
  75. return i; //找到就返回邻接结点的坐标
  76. }
  77. }
  78. return -1; //没找到的话,返回-1
  79. }
  80. //DFS实现,先定义一个isVisited布尔数组确认该点是否遍历过
  81. public void DFS(int index,boolean[] isVisited){
  82. System.out.print(getVertexByIndex(index)+" "); //打印当前结点
  83. isVisited[index] = true;
  84. //查找index的第一个邻接结点f
  85. int f = getFirstNeighbor(index);
  86. //
  87. while(f != -1){//说明有
  88. if(!isVisited[f]){//f没被访问过
  89. DFS(f,isVisited); //就进入该节点f进行遍历
  90. }
  91. //如果f已经被访问过,从当前 i 行的 f列 处往后找
  92. f = getNextNeighbor(index,f);
  93. }
  94. }
  95. //考虑到连通分量,需要对所有结点进行一次遍历,因为有Visited,所以不用考虑冲突情况
  96. public void DFS(){
  97. for(int i=0;i<vertexList.size();i++){
  98. if(!isVisited[i]){
  99. DFS(i,isVisited);
  100. }
  101. }
  102. }
  103. public void BFS(int index,boolean[] isVisited){
  104. //BFS是由队列实现的,所以我们先创建一个队列
  105. LinkedList<Integer> queue = new LinkedList<>();
  106. System.out.print(getVertexByIndex(index)+" "); //打印当前结点
  107. isVisited[index] =true; //遍历标志ture
  108. queue.addLast(index); //队尾加入元素
  109. int cur,neighbor; //队列头节点cur和邻接结点neighbor
  110. while(!queue.isEmpty()){//如果队列不为空的话,就一直进行下去
  111. //取出队列头结点下标
  112. cur = queue.removeFirst(); //可以用作出队
  113. //得到第一个邻接结点的下标
  114. neighbor = getFirstNeighbor(cur);
  115. //之后遍历下一个
  116. while(neighbor != -1){//邻接结点存在
  117. //是否访问过
  118. if(!isVisited[neighbor]){
  119. System.out.print(getVertexByIndex(neighbor)+" ");
  120. isVisited[neighbor] = true;
  121. queue.addLast(neighbor);
  122. }
  123. //在cur行找neighbor列之后的下一个邻接结点
  124. neighbor = getNextNeighbor(cur,neighbor);
  125. }
  126. }
  127. }
  128. //考虑到连通分量,需要对所有结点进行一次遍历,因为有Visited,所以不用考虑冲突情况
  129. public void BFS(){
  130. for(int i=0;i<vertexList.size();i++){
  131. if(!isVisited[i]){
  132. BFS(i,isVisited);
  133. }
  134. }
  135. }
  136. public void prim(int begin){
  137. //Prim原理:从当前集合选出权重最小的邻接结点加入集合,构成新的集合,重复步骤,直到N-1条边
  138. int N = vertexList.size();
  139. //当前的集合 与其他邻接结点的最小值
  140. int[] lowcost = edges[begin];
  141. //记录该结点是从哪个邻接结点过来的
  142. int[] adjvex = new int[N];
  143. Arrays.fill(adjvex,begin);
  144. //表示已经遍历过了,isVisited置true
  145. isVisited[begin] = true;
  146. for(int i =0;i<N-1;i++){//进行N-1次即可,因为只需要联通N-1条边
  147. //寻找当前集合最小权重邻接结点的操作
  148. int index = 0;
  149. int mincost = Integer.MAX_VALUE;
  150. for(int j = 0;j<N;j++){
  151. if(isVisited[j]) continue;
  152. if(lowcost[j] < mincost){//寻找当前松弛点
  153. mincost = lowcost[j];
  154. index = j;
  155. }
  156. }
  157. System.out.println("选择节点"+index+"权重为:"+mincost);
  158. isVisited[index] = true;
  159. System.out.println(index);
  160. //加入集合后更新的操作,看最小邻接结点是否更改
  161. for(int k = 0;k<N;k++){
  162. if(isVisited[k]) continue;//如果遍历过就跳过
  163. if(edges[index][k] < lowcost[k]){ //加入新的节点之后更新,检查原图的index节点,加入后,是否有更新的
  164. lowcost[k] = (edges[index][k]);
  165. adjvex[k] = index;
  166. }
  167. }
  168. }
  169. }
  170. //用于对之前定义的to进行排序
  171. public void toSort(){
  172. Collections.sort(this.to, new Comparator<EData>() {
  173. @Override
  174. public int compare(EData o1, EData o2) {
  175. //顺序对就是升序,顺序不对就是降序,比如我现在是o1和o2传进来,比较时候o1在前就是升序,o1在后就是降序
  176. int result = Integer.compare(o1.weight,o2.weight);
  177. return result;
  178. }
  179. });
  180. }
  181. //并查集查找
  182. public int find(int x,int[] f){
  183. while(x!=f[x]){
  184. x = f[x];
  185. }
  186. return x;
  187. }
  188. //并查集合并
  189. public int union(int x,int y,int[] f){
  190. find(x,f);
  191. find(y,f);
  192. if(x!=y){
  193. f[x] = y;
  194. return y;
  195. }
  196. return -1; //如果一样的集合就返回-1
  197. }
  198. public ArrayList<Integer> kruskal(){
  199. //kruskal是对form to weight这种结构的类进行排序,然后选取最短的一条边,看是否形成回路,加入
  200. toSort(); //调用toSort进行排序
  201. //由于要判断是否形成回路,我们可以用DFS或者BFS,因为之前都写过,所以我们在这用并查集
  202. //初始化并查集
  203. int[] f = new int[vertexList.size()];
  204. for(int i = 0;i<vertexList.size();i++){
  205. f[i] = i;
  206. }
  207. ArrayList<Integer> res = new ArrayList<>();
  208. int count = 0;
  209. for(int i = 0;count != vertexList.size()-1 && i<this.to.size();i++){
  210. //之后就是开始取边了
  211. EData temp = this.to.get(i);
  212. if(union(temp.start,temp.end,f)!=-1){//如果查到不是一个集,就可以添加
  213. //这里都加进来是方便看哪个边
  214. res.add(temp.start);
  215. res.add(temp.end);
  216. count++;
  217. }
  218. }
  219. return res; //最后把集合返回去就行
  220. }
  221. public static void main(String[] args) {
  222. int n = 5;
  223. String[] Vertexs ={"A","B","C","D","E"};
  224. //创建图对象
  225. Graph graph = new Graph(n);
  226. for(String value:Vertexs){
  227. graph.insertVertex(value);
  228. }
  229. graph.insertEdge(0,1,7);
  230. graph.insertEdge(0,2,1);
  231. graph.insertEdge(1,2,6);
  232. graph.insertEdge(1,3,3);
  233. graph.insertEdge(1,4,5);
  234. graph.insertEdge(3,4,8);
  235. graph.showGraph();
  236. graph.DFS(1, graph.isVisited);
  237. System.out.println();
  238. graph.DFS();//再求求所有的,看有没有剩下的
  239. System.out.println();
  240. Arrays.fill(graph.isVisited,false);
  241. graph.BFS(1, graph.isVisited);
  242. System.out.println();
  243. Arrays.fill(graph.isVisited,false);
  244. graph.BFS();
  245. System.out.println();
  246. Arrays.fill(graph.isVisited,false);
  247. graph.prim(2);
  248. graph.toSort();
  249. for(EData i : graph.to){
  250. System.out.println(i.toString());
  251. }
  252. System.out.println(graph.kruskal().toString());;
  253. }
  254. }
  255. class EData{
  256. //当然,这是为了方便,直接记录结点下标,而不记录像"A"这种
  257. int start;
  258. int end;
  259. int weight;
  260. EData(int start,int end,int weight){
  261. this.start = start;
  262. this.end = end;
  263. this.weight = weight;
  264. }
  265. @Override
  266. public String toString() {
  267. return "EData{" +
  268. "start=" + start +
  269. ", end=" + end +
  270. ", weight=" + weight +
  271. '}';
  272. }
  273. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/417329
推荐阅读
相关标签
  

闽ICP备14008679号