您的位置:首页 > 其它

最小生成树算法Prim、Kruskal

2015-09-23 21:33 399 查看
在看本文之前最好先看看算法分析之最小生成树

在介绍两种算法之前,我需要先介绍一个推论首先介绍一个概念,连通分量:其实就是一棵树(某些树也可能是一个节点)

推论:对于无向连通图G=(V,E)。设A是G的某棵最小生成树的子集,并设C=(Vc,Ec)为森林Ga=(V,A)的一个连通分量,如果边(u,v)是连接C和Ga中某个其他连通分量的一条轻量级边,则(u,v)对于A来说是安全的。

Kruskal算法

在所有的连接两颗树的边里找到权重最小的边(u,v),设C1,C2分别为u,v所连接的两个连通分量由于(u,v)一定是连接两个连通分量C1,C2的轻量级边,由推论可知(u,v)是安全的

算法伪代码:

A=null

对G中的边E按权重w对每条边安卓小到大的顺序进行排序

for each (u,v)∈G.E{

如果u,v不属于同一颗树{

A=A∪{(u,v)}

将u,v所在的树合并成一棵树

}

}

具体过程可以用下图来说明













算法模板:

public class Mian
{
	public static void main(String[] args)
	{
		/**
		 * 对fatherV初始化
		 **/
		for (int i = 0; i < fatherV.length; i++)
		{
			fatherV[i]=i;
		}
		/**
		 * 给e赋值
		 **/
		//TODO
		
		/**
		 * 给V赋值
		 **/
		//TODO
		kruskal();
	}
	
	static int V;//结点的个数
	static int[] fatherV=new int[100];//farher[i]=j表示结点i的父节点为j
	static Edge[] e=new Edge[100];
	static class Edge{
		int u,v,w;
		Edge(int u,int v,int w){
			this.u=u;
			this.v=v;
			this.w=w;
		}
	}
	/**
	 * 寻找结点x所在树的根节点
	 **/
	static int findRoot(int x)
	{
		return (x==fatherV[x]) ? x : findRoot(fatherV[x]);
	}
	/**
	 * 判断结点u,v是否属于同一棵树,如果不属于同一棵树就将u,v所在的树合并为一棵树
	 **/
	static boolean judgeIsSameTree(int u,int v)
	{
		int root1=findRoot(u);
		int root2=findRoot(v);
		if(root1==root2)//u,v在同一棵树中
		{
			return true;
		}
		else {
			fatherV[u]=v;//将u,v所在的两颗树合并
		}
		return false;
	}
	
	static void kruskal()
	{
		boolean flag=false;
		int numEgdeInTree=0;
		quickSort(0, e.length);
		for(int i=0;i<e.length;i++)
		{
			if(!judgeIsSameTree(e[i].u, e[i].v))
			{
				numEgdeInTree++;
				if(numEgdeInTree==V-1)//找到了一棵最小生成树
				{
					flag=true;
					break;
				}
			}
		}
		if(flag)
		{
			//TODO
		}
		else {
			System.out.println("can not find a minimal spanning tree");
		}
	}
	/**
	 * 快排对e按w进行升序排序,这里排序可以随意选择方法,也可以用语言给定好的排序函数
	 **/
	static void quickSort(int l,int r){
		if(l>=r){
			return;
		}
		int q=partition(l, r);
		quickSort(l, q-1);
		quickSort(q+1, r);
	}
	
	static int partition(int l,int r){
		int i=l;int j=r;int x=e[i].w;
		while(i<j){
			while(i<j && e[j].w>=x) j--;
			if(i<j){
				e[i].w=e[j].w;
				i++;
			}else {
				break;
			}
			while(i<j && e[i].w<=x) i++;
			if(i<j){
				e[j].w=e[i].w;
				j--;
			}else {
				break;
			}
		}
		//显然退出时i=j
		e[i].w=x;
		return i;
	}

}


Prim算法

算法的每一步都寻找一条横跨切割(A,V-A)的轻量级边作为A的安全边

设v.key为v到A中每个结点的边中最小边的权重

伪代码:

A=null

Q=G.V

任意确定一个点作为root加入到A中

while(Q!=null){

从Q找出结点u,u.key是Q中的所有结点中最小的

将u加入到A并从Q中删除

更新与u相连的结点的v.key

}

用下面的一系列图来说明:













prim模板如下:

public class Main
{
	public static void main(String[] args)
	{
		prim();
	}
	
	static int[] low=new int[100];//low[i]=d表示结点i到A中的结点的最小边的权值为d
	static int used[]=new int[100];//used[i]=1,0分别表示i结点在集合A中和i结点不在集合A中
	static int n;//图中结点的数目
	static int[][] w=new int[100][100];//w[i][j]结点i到j的边的权值
	static void prim(){
		//初始化所有结点的low值全部设为无限大
		for(int i=0;i<low.length;i++)
		{
			low[i]=2147483647;
		}
		//以结点0为初始结点,标记该结点
		int pos=0;used[pos]=1;
		for(int i=1;i<n;i++)
		{
			low[i]=w[pos][i];
		}
		
		for(int i=1;i<n;i++)
		{
			
			/**
			 * 找出集合A到A以外的结点的边的权值最小的结点
			 **/
			int min_low=1000000;
			for(int j=1;j<n;j++)
			{
				if(used[j]==0 && low[j]<min_low)
				{
					min_low=low[j];
					pos=j;
				}
			}
			/**
			 * 将找到的结点加入到集合A,并更新与该结点相连的结点的low值
			 **/
			used[pos]=1;
			for(int j=1;j<n;j++)
			{
				if(used[j]==0 && w[pos][j]<low[j])
				{
					low[j]=w[pos][j];
					//TODO,结点j的父节点为pos
				}
			}
			
		}

	}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: