最小生成树(Prim算法和Kruskal算法算法详解)

前言

在数据结构与算法的图论中,(生成)最小生成树算法是一种经常使用而且和生活贴切比较近的一种算法。可是可能不少人对概念不是很清楚。咱们看下百度百科对于最小生成树定义java

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

通俗易懂的讲就是最小生成树包含原图的全部节点而只用最少的边最小的权值距离。由于n个节点最少须要n-1个边联通,而距离就须要采起某种策略选择恰当的边。数组

从定义上分析,最小生成树实际上是一种能够看做是树的结构。而最小生成树的结构来源于图(尤为是有环状况)。经过这个图咱们使用某种算法造成最小生成树的算法就能够叫作最小生成树算法。具体实现上有两种实现方法、策略分别为kruskal算法和prim算法。数据结构

学习最小生成树实现算法以前咱们要先高清最小生成树的结构和意义所在。咱么首先根据一些图更好的祝你理解。ide

一个故事

在中国城市道路规划中,是一门很须要科学的研究(只是假设学习没必要当真)。城市道路铺设可能经历如下几个阶段。函数

  • 初始,各个城市没有高速公路(铁路)。城市没有!
  • 政府打算各个城市铺设公路(铁路),每一个城市都想成为交通枢纽,快速到达其余城市!可是这种状况下国家集体资源跟不上、造价太昂贵。而且形成巨大浪费!
  • 最终国家选择一些主要城市进行联通,有个别城市只能稍微绕道而行,而绕道太远的、人流量多的国家考虑新建公路(铁路)。适当提升效率。

在这里插入图片描述
而随着国家科技互联网的进步,须要铺设 高科技黄金外嵌光缆管道 (黄金夸张)联通整个国家使得信息可以快速传统、联通。(注意,我们的通道是黄金的)对于有些可能重复的环。势必形成浪费。

因此咱们要从有环图中选取代价和最小的路线一方面代价最小(总距离最小最省黄金)另外一方面联通全部城市学习

然而根据上图咱们能够获得如下最小生成树: this

在这里插入图片描述
惟物辩证法认为

  • 问题的主要矛盾对问题起着决定性做用。主要矛盾次要矛盾相互影响,相互渗透,必定程度能够相互转化。故咱们看问题要抓关键、找核心
  • 公路时代城市联通的主要矛盾是时间慢,而造价相比运输时间是次要矛盾。因此在公路时代咱们尽可能使得城市可以直接联通,缩短城市联系时间。而稍微考虑建路成本!随着科技发展、信息传输相比公路运输很快,从而事件的主要矛盾从运输时间转变为造价成本。因此咱们会关注联通全部点的路程(最短)。这就用到最小生成树算法。

而相似的还有局部区域岛屿联通修桥,海底通道这些高成本的都多多少少会运用。spa

Kruskal算法

上面介绍了最小生成树是什么,可是咱们须要掌握和理解最小生成树如何造成。给你一个图,生成一个最小生成树,固然须要必定规则。而在实现最小生成树方面有prim和kruskal算法,这两种算法的策略有所区别,可是时间复杂度一致。code

百度百科定义的基本思想

先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点当作各棵树上的根结点,以后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不一样的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。

简而言之,Kruskal算法进行调度的单位是边,它的信仰为:全部边能小则小,算法的实现方面和并查集(不相交集合)很像,要用到并查集判断两点是否在同一集合。

而算法的具体步骤为:

  1. 将边(以及2顶点)的对象依次加入集合(优先队列)q1中。初始全部点相互独立
  2. 取出当前q1最小边,判断边的两点是否联通。
  3. 若是联通,跳过,若是不连通,则使用union(并查集合并)将两个顶点合并。这条边被使用(能够储存或者计算数值)。
  4. 重复2,3操做直到集合(优先队列)q1为空。此时被选择的边构成最小生成树。

在这里插入图片描述
在这里插入图片描述

Prim算法

除了Kruskal算法之外,普里姆算法(Prim算法)也是经常使用的最小生成树算法。虽然在效率上差很少。可是贪心的方式和Kruskal彻底不一样。prim算法的核心信仰是:从已知扩散寻找最小。它的实现方式和Dijkstra算法类似但稍微有所区别,Dijkstra是求单源最短路径。而每计算一个点须要对这个点重新更新距离。而prim甚至不用更新距离。直接找已知点的邻边最小加入便可!

对于具体算法具体步骤,大体为:

  1. 寻找图中任意点,以它为起点,它的全部边V加入集合(优先队列)q1,设置一个boolean数组bool[]标记该位置已经肯定。
  2. 从集合q1找到距离最小的那个边v1判断边另外一点p是否被标记(访问),若是p被标记说明已经肯定那么跳过,若是未被标(访问)记那么标记该点p,而且与p相连的未知点(未被标记)构成的边加入集合q1边v1(能够进行计算距离之类,该边构成最小生成树) .
  3. 重复1,2直到q1为空,构成最小生成树 !

大致步骤图解为:

在这里插入图片描述
在这里插入图片描述

由于prim从开始到结束一直是一个总体在扩散,因此不须要考虑两棵树合并的问题,在这一点实现上稍微方便了一点。

固然,要注意的是最小生成树并不惟一,甚至同一种算法生成的最小生成树均可能有所不一样,可是相同的是不管生成怎样的最小生成树:

  • 可以保证全部节点连通(可以知足要求和条件)
  • 可以保证全部路径之和最小(结果和目的相同)
  • 最小生成树不惟一,可能多样的
    在这里插入图片描述

代码实现

上面分析了逻辑实现。下面咱们用代码简单实现上述的算法。

prim
package 图论;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;

public class prim {

	public static void main(String[] args) {
		int minlength=0;//最小生成树的最短路径长度
		int max=66666;
		String cityname[]= {"北京","武汉","南京","上海","杭州","广州","深圳"};
		int city[][]= {
				{ max, 8, 7, max, max, max, max }, //北京和武汉南京联通
				{ 8, max,6, max,9, 8,max }, //武汉——北京、南京、杭州、广州
				{ 7, 6, max, 3,4, max,max }, //南京——北京、武汉、上海、杭州
				{ max, max,3, max,2, max,max }, //上海——南京、杭州
				{ max, 9,4, 2,max, max,10 }, //杭州——武汉、南京、上海、深圳
				{ max, 8,max, max,max, max,2 }, //广州——武汉、深圳
				{ max, max,max, max,10,2,max }//深圳——杭州、广州
		};// 地图

		boolean istrue[]=new boolean[7];
		//南京
		Queue<side>q1=new PriorityQueue<side>(new Comparator<side>() {
			public int compare(side o1, side o2) {
				// TODO Auto-generated method stub
				return o1.lenth-o2.lenth;
			}
		});
		for(int i=0;i<7;i++)
		{
			if(city[2][i]!=max)
			{
				istrue[2]=true;
				q1.add(new side(city[2][i], 2, i));
			}
		}		
		while(!q1.isEmpty())
		{
			side newside=q1.poll();//抛出
			if(istrue[newside.point1]&&istrue[newside.point2])
			{
				continue;
			}
			else {
				if(!istrue[newside.point1])
				{
					istrue[newside.point1]=true;
					minlength+=city[newside.point1][newside.point2];
					System.out.println(cityname[newside.point1]+" "+cityname[newside.point2]+" 联通");
					for(int i=0;i<7;i++)
					{
						if(!istrue[i])
						{
							q1.add(new side(city[newside.point1][i],newside.point1,i));
						}
					}
				}
				else {
					istrue[newside.point2]=true;
					minlength+=city[newside.point1][newside.point2];
					System.out.println(cityname[newside.point2]+" "+cityname[newside.point1]+" 联通");
					for(int i=0;i<7;i++)
					{
						if(!istrue[i])
						{
							q1.add(new side(city[newside.point2][i],newside.point2,i));
						}
					}
				}
			}
			
		}
		System.out.println(minlength);		
	}
	
	static class side//边 {
		int lenth;
		int point1;
		int point2;
		public side(int lenth,int p1,int p2) {
			this.lenth=lenth;
			this.point1=p1;
			this.point2=p2;
		}
	}

}

复制代码

实现效果:

在这里插入图片描述

Kruskal:
package 图论;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

import 图论.prim.side;
/* * 做者:bigsai(公众号) */
public class kruskal {

	static int tree[]=new int[10];//bing查集
	public static void init() {
		for(int i=0;i<10;i++)//初始
		{
			tree[i]=-1;
		}
	}
	public static int search(int a)//返回头节点的数值 {
		if(tree[a]>0)//说明是子节点
		{
			return tree[a]=search(tree[a]);//路径压缩
		}
		else
			return a;
	}
	public static void union(int a,int b)//表示 a,b所在的树合并小树合并大树(不重要) {
		int a1=search(a);//a根
		int b1=search(b);//b根
		if(a1==b1) {//System.out.println(a+"和"+b+"已经在一棵树上");
		}
		else {
		if(tree[a1]<tree[b1])//这个是负数,为了简单减小计算,不在调用value函数
		{
			tree[a1]+=tree[b1];//个数相加 注意是负数相加
			tree[b1]=a1;       //b树成为a的子树,直接指向a;
		}
		else
		{
			tree[b1]+=tree[a1];//个数相加 注意是负数相加
			tree[a1]=b1;       //b树成为a的子树,直接指向a;
		}
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		init();
		int minlength=0;//最小生成树的最短路径长度
		int max=66666;
		String cityname[]= {"北京","武汉","南京","上海","杭州","广州","深圳"};
		boolean jud[][]=new boolean[7][7];//加入边须要防止重复 好比 ba和ab等价的
		int city[][]= {
				{ max, 8, 7, max, max, max, max }, 
				{ 8, max,6, max,9, 8,max }, 
				{ 7, 6, max, 3,4, max,max }, 
				{ max, max,3, max,2, max,max }, 
				{ max, 9,4, 2,max, max,10 }, 
				{ max, 8,max, max,max, max,2 }, 
				{ max, max,max, max,10,2,max }
		};// 地图
		boolean istrue[]=new boolean[7];
		//南京
		Queue<side>q1=new PriorityQueue<side>(new Comparator<side>() {//优先队列存边+
			public int compare(side o1, side o2) {
				// TODO Auto-generated method stub
				return o1.lenth-o2.lenth;
			}
		});
		for(int i=0;i<7;i++)
		{
			for(int j=0;j<7;j++)
			{
				if(!jud[i][j]&&city[i][j]!=max)//是否加入队列
				{
					jud[i][j]=true;jud[j][i]=true;
					q1.add(new side(city[i][j], i, j));
				}
			}
		}
		while(!q1.isEmpty())//执行算法
		{
			side newside=q1.poll();
			int p1=newside.point1;
			int p2=newside.point2;
			if(search(p1)!=search(p2))
			{
				union(p1, p2);
				System.out.println(cityname[p1]+" "+cityname[p2]+" 联通");
				minlength+=newside.lenth;
			}
		}
		System.out.println(minlength);
		

	}
	static class side//边 {
		int lenth;
		int point1;
		int point2;
		public side(int lenth,int p1,int p2) {
			this.lenth=lenth;
			this.point1=p1;
			this.point2=p2;
		}
	}
}

复制代码
kruskal

在这里插入图片描述

总结

最小生成树算法理解起来也相对简单,实现起来也不是很难。Kruskal和Prim主要是贪心算法的两种角度。一个从总体开始找最小边,遇到关联不断合并,另外一个从局部开始扩散找身边的最小不断扩散直到生成最小生成树。在学习最小生成树以前最好学习一下dijkstra算法和并查集,这样在实现起来可以快一点,清晰一点。

最后,若是你那天真的得到一大笔资金去修建这么一条昂贵的黄金路线,能够适当采起此方法,另外剩下的大批,,苟富贵,勿相忘。。

若是感受还行,还请点个赞,关注一下吧,关注笔者公众号: bigsai回复数据结构便可得到数据结构的学习资料和视频一份!

相关文章
相关标签/搜索