克鲁斯卡尔算法(kruskal)

算法背景:

图中顶点A,B,C,D,E,F,G七个顶点表示七个村庄,现要修路,使得任意两个村庄之间都有通路,即都能到达(可以不是直达),问怎样修路可以使修路的总里程最短?

思路:

将所有边都连接起来,肯定都有通路,但总里程数一定不是最小。应该使用尽量少的路线,并使每条路线的长度最小,便可保证总里程最小。

最小生成树:

修路问题的本质就是最小生成树问题,最小生成树:Minimum Cost Spanning Tree,简称MST。具体为:给定一个带权的无向图,如何选取生成树,使得所有边上权的总和最小。

最小生成树特征:

  • 有N个顶点,则N-1条边。
  • 包含了图的全部顶点。
  • N-1条边都在图中。

克鲁斯卡尔算法求最小生成树:

算法思路:

不同于普里姆算法的从点着手,克鲁斯卡尔算法以边为着手点,在所有的边的权值从小到大排序后,依次选边,使得在不构成回路的情况下形成最小生成树。

重点是对能否构成回路的判断:用一个end数组记录各顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点"(单个顶点的终点是自身)。然后每次需要将一条边添加到最小生存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。

判断回路核心代码:

//求某一顶点的终点,若end数组为空,则终点是自身,否则终点就是在end数组中不断往后直到为0时的点。
//例如:end{0,3,4,4,0},B顶点与D顶点相连,D顶点又与E顶点相连,所以B的终点就是E顶点
//而C顶点的终点也是E顶点,所以当B与C相连时,就会构成回路。
private int getEnd(int []end,int i) {
	while(end[i] != 0) {
		i = end[i];
	}
	return i;
}

kruskal核心代码:

算法分析:

克鲁斯卡尔算法的外层循环是对图的所有排好序的边进行遍历,可以对内层在求索引时进行优化,即边的类中直接保存两端点的索引,不保存两端点的“名”,但由于输出时需要输出顶点名,所以可将边类设置成内部类,便可利用索引在图类的属性vertex找出顶点名。

算法的内层主要是找顶点的终点,即克鲁斯卡尔算法只取决于边,与顶点无关。适合于图中的顶点很多,但连接的边不多的情况,即适用于稀疏图。

public void kruskal() {
	int []end = new int[vertex.size()];//保存终点的数组
	Edge []result = new Edge[vertex.size() - 1];//保存最小生成树
	Edge[] edgeAll = getEdgeAndSort();//得到图中排好序的边
	int j = 0;
	int sum = 0;//累积里程
	for(int i = 0; i < edgeAll.length;i++) {//遍历排好序的边
		int start = getIndex(edgeAll[i].start);//边的一个顶点索引
		int last = getIndex(edgeAll[i].end);//边的另一个顶点索引
		int m = getEnd(end, start);//求该索引动态连接的终点
		int n = getEnd(end, last);
		if(m != n) {
			end[m] = n;//在向生成树中加入顶点时更新开始一端的终点
			result[j++] = edgeAll[i];
			sum += edgeAll[i].weight;
		}
	}
	System.out.println();
	for(Edge data : result) {//输出生成树
		System.out.println(data);
	}
	System.out.println("需要修的总里程是" + sum + "米。");
}

java代码:

import java.util.*;

public class Kruskal {
	public static void main(String[] args) {
		final int INF = Integer.MAX_VALUE;
		String []vertex = {"A","B","C","D","E","F","G"};
		int [][]edge = new int[][] {
			{INF,12,INF,INF,INF,16,14},
			{12,INF,10,INF,INF,7,INF},
			{7,10,INF,3,5,6,INF},
			{INF,INF,3,INF,4,INF,INF},
			{INF,INF,5,4,INF,2,8},
			{16,7,6,INF,2,INF,9},
			{14,INF,INF,INF,8,9,INF}
		};
		Graph graph = new Graph(vertex, edge);
		graph.printMatrix();
		graph.kruskal();
	}
}
class Graph{
	static final int INF = Integer.MAX_VALUE;
	ArrayList<String> vertex;
	int[][]edge;
	int numOfEdge;
	
	public Graph(String vertex[], int[][] edge) {
		int len = vertex.length;
		this.vertex = new ArrayList<String>(len);
		this.edge = new int [len][len];
		for(int i = 0;i < len;i++) {
			this.vertex.add(vertex[i]);
			for(int j = 0;j < len;j++) {
				this.edge[i][j] = edge[i][j];
			}
		}
		for(int i = 0; i < len;i++) {
			for(int j = i + 1;j < len;j++) {
				if(edge[i][j] != INF) {
					numOfEdge++;
				}
			}
		}
	}
	
	public void printMatrix() {
		System.out.println("邻接矩阵表示为:");
		for(int[] data : edge) {
			System.out.println(Arrays.toString(data));
		}
	}
	
	public Edge[] getEdgeAndSort() {
		int index = 0;
		Edge[] allEdge = new Edge[numOfEdge];
		for(int i = 0; i < vertex.size();i++) {
			for(int j = i + 1; j < vertex.size();j++) {
				if(edge[i][j] != INF) {
					allEdge[index++] = new Edge(vertex.get(i), vertex.get(j), edge[i][j]);
				}
			}
		}
		Arrays.sort(allEdge);
		return allEdge;
	}
	
	private int getIndex(String s) {
		for(int i = 0; i < vertex.size();i++) {
			if(vertex.get(i).equals(s)) {
				return i;
			}
		}
		return -1;
	}
	
	private int getEnd(int []end,int i) {
		while(end[i] != 0) {
			i = end[i];
		}
		return i;
	}
	
	public void kruskal() {
		int []end = new int[vertex.size()];
		Edge []result = new Edge[vertex.size() - 1];
		Edge[] edgeAll = getEdgeAndSort();
		int j = 0;
		int sum = 0;
		for(int i = 0; i < edgeAll.length;i++) {
			int start = getIndex(edgeAll[i].start);
			int last = getIndex(edgeAll[i].end);
			int m = getEnd(end, start);
			int n = getEnd(end, last);
			if(m != n) {
				end[m] = n;
				result[j++] = edgeAll[i];
				sum += edgeAll[i].weight;
			}
		}
		System.out.println();
		for(Edge data : result) {
			System.out.println(data);
		}
		System.out.println("需要修的总里程是" + sum + "米。");
	}
}

class Edge implements Comparable<Edge>{
	String start;
	String end;
	int weight;
	
	public Edge(String start, String end, int weight) {
		this.start = start;
		this.end = end;
		this.weight = weight;
	}

	@Override
	public String toString() {
		return "应连接:(" + start + "," + end + "),权值(距离)为:" + weight;
	}

	@Override
	public int compareTo(Edge o) {
		return this.weight - o.weight;
	}
}

程序输出:

邻接矩阵表示为:
[2147483647, 12, 2147483647, 2147483647, 2147483647, 16, 14]
[12, 2147483647, 10, 2147483647, 2147483647, 7, 2147483647]
[7, 10, 2147483647, 3, 5, 6, 2147483647]
[2147483647, 2147483647, 3, 2147483647, 4, 2147483647, 2147483647]
[2147483647, 2147483647, 5, 4, 2147483647, 2, 8]
[16, 7, 6, 2147483647, 2, 2147483647, 9]
[14, 2147483647, 2147483647, 2147483647, 8, 9, 2147483647]

应连接:(E,F),权值(距离)为:2
应连接:(C,D),权值(距离)为:3
应连接:(D,E),权值(距离)为:4
应连接:(B,F),权值(距离)为:7
应连接:(E,G),权值(距离)为:8
应连接:(A,B),权值(距离)为:12
需要修的总里程是36米。

优化后:(边类设置为内部类)

import java.util.*;

public class Kruskal {
	public static void main(String[] args) {
		final int INF = Integer.MAX_VALUE;
		String []vertex = {"A","B","C","D","E","F","G"};
		int [][]edge = new int[][] {
			{INF,12,INF,INF,INF,16,14},
			{12,INF,10,INF,INF,7,INF},
			{7,10,INF,3,5,6,INF},
			{INF,INF,3,INF,4,INF,INF},
			{INF,INF,5,4,INF,2,8},
			{16,7,6,INF,2,INF,9},
			{14,INF,INF,INF,8,9,INF}
		};
		Graph graph = new Graph(vertex, edge);
		graph.printMatrix();
		graph.kruskal();
	}
}
class Graph{
	static final int INF = Integer.MAX_VALUE;
	ArrayList<String> vertex;
	int[][]edge;
	int numOfEdge;
	
	class Edge implements Comparable<Edge>{
		int start;
		int end;
		int weight;
		
		public Edge(int start, int end, int weight) {
			this.start = start;
			this.end = end;
			this.weight = weight;
		}

		@Override
		public String toString() {
			return "应连接:(" + vertex.get(start) + "," + vertex.get(end) + "),权值(距离)为:" + weight;
		}

		@Override
		public int compareTo(Edge o) {
			return this.weight - o.weight;
		}
	}
	
	public Graph(String vertex[], int[][] edge) {
		int len = vertex.length;
		this.vertex = new ArrayList<String>(len);
		this.edge = new int [len][len];
		for(int i = 0;i < len;i++) {
			this.vertex.add(vertex[i]);
			for(int j = 0;j < len;j++) {
				this.edge[i][j] = edge[i][j];
			}
		}
		for(int i = 0; i < len;i++) {
			for(int j = i + 1;j < len;j++) {
				if(edge[i][j] != INF) {
					numOfEdge++;
				}
			}
		}
	}
	
	public void printMatrix() {
		System.out.println("邻接矩阵表示为:");
		for(int[] data : edge) {
			System.out.println(Arrays.toString(data));
		}
	}
	
	public Edge[] getEdgeAndSort() {
		int index = 0;
		Edge[] allEdge = new Edge[numOfEdge];
		for(int i = 0; i < vertex.size();i++) {
			for(int j = i + 1; j < vertex.size();j++) {
				if(edge[i][j] != INF) {
					allEdge[index++] = new Edge(i, j, edge[i][j]);
				}
			}
		}
		Arrays.sort(allEdge);
		return allEdge;
	}
	
	private int getEnd(int []end,int i) {
		while(end[i] != 0) {
			i = end[i];
		}
		return i;
	}
	
	public void kruskal() {
		int []end = new int[vertex.size()];
		Edge []result = new Edge[vertex.size() - 1];
		Edge[] edgeAll = getEdgeAndSort();
		int j = 0;
		int sum = 0;
		for(int i = 0; i < edgeAll.length;i++) {
			int start = edgeAll[i].start;
			int last = edgeAll[i].end;
			int m = getEnd(end, start);
			int n = getEnd(end, last);
			if(m != n) {
				end[m] = n;
				result[j++] = edgeAll[i];
				sum += edgeAll[i].weight;
			}
		}
		System.out.println();
		for(Edge data : result) {
			System.out.println(data);
		}
		System.out.println("需要修的总里程是" + sum + "米。");
	}
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法都是用于解决最小生成树问题的算法最小生成树问题是指在一个无向连通中,找到一棵生成树,使得树上所有边的权值之和最小。生成树是指一个无向的生成子,它是一棵树,且包含中所有顶点。 下面我们分别介绍普里姆算法克鲁斯卡尔算法: 1. 普里姆算法 普里姆算法是一种贪心算法,它从一个任意点开始,逐步扩展生成树,每次选择当前生成树到未加入的点中距离最近的点,并将其加入生成树。 具体实现步骤如下: - 随机选择一个起始点,将其加入生成树。 - 在生成树中的所有节点中,找到到未加入生成树的节点中距离最小的节点,将其加入生成树。 - 重复以上步骤,直到生成树包含了所有节点。 2. 克鲁斯卡尔算法 克鲁斯卡尔算法也是一种贪心算法,它从边集合中选择边,逐步扩展生成树,每次选择当前边集合中权值最小的边,并将其加入生成树。 具体实现步骤如下: - 将所有边按照权值从小到大排序。 - 从权值最小的边开始,逐个加入生成树,如果加入当前边会形成环,则不加入该边。 - 重复以上步骤,直到生成树包含了所有节点。 两种算法的时间复杂度都是O(ElogE),其中E为边数。普里姆算法在处理稠密时效率更高,而克鲁斯卡尔算法在处理稀疏时效率更高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值