Kruskal算法和prim算法求最小生成树学习小结(JAVA)

prim算法是用来实现图最小生成树的2种常用方法之一,Prim算法的主要步骤如下:


  1.设图的顶点集为V,首先选取一个点作为起始点,比如说1顶点,加入到U集合中

2.在所有u∈U,v∈V-U的边(u,v)∈E中,找一条权最小的边(u,v),将此边加进集合T中,并将此边的非U中顶点加入U中。此步骤的功能是在边集E中找一条边,要求这条边满足以下条件:首先边的两个顶点要分别在顶点集合U和V-U中,其次边的权要最小。找到这条边以后,把这条边放到边集T中,并把这条边上不在U中的那个顶点加入到U中。

3:如果U=V,则算法结束;否则重复步骤2。可以把本步骤看成循环终止条件。
我们可以算出当U=V时,步骤2共执行了n-1次(设n为图中顶点的数目),T中也增加了n-1条边,这n-1条边就是需要求出的最小生成树的边。

例题:给出t组数据,每组数据给出图的顶点数n,然后下面是n*n的无向图邻接矩阵表示,求最小生成树中权最大的边的权值。样例如下:


Sample Input

1

3
0 990 692
990 0 179
692 179 0

Sample Output

692

解法一:Prim算法代码

import java.util.Scanner;
public class Main{

private int prim[][];//图的邻接矩阵
private boolean visit[];//记录顶点是否被访问,即是否加入到了顶点集U
private int Len[]; //Len[i] 记录顶点集U到i的最短距离,即U中所有点到i的距离之最小者。
private int n;//顶点数
int ans;

public Main(int n,int[][] prim){
this.n=n;
this.prim=prim;
visit=new boolean[n+1];//开始时都未访问
Len=new int[n+1];
}

private int prim_solve(int xx){
int minx;int k=0;

ans = -1;
for (int i = 1; i <= n; i++)
{
Len[i] = prim[xx][i];
}
Len[xx] = 0;
visit[xx] = true; //此时U中只有起点xx
for(int i = 1; i< n; i++) // 注意:因为xx起点已经访问过,所以只需再访问n-1个
{
minx = Integer.MAX_VALUE;
for(int j = 1; j <= n; j++ ) //在所有u∈U,v∈V-U的边(u,v)∈E中,找一条权最小的边(u,v)
{ //这里找的是:与顶点集U相邻的距离最小值
if ( !visit[j] && Len[j] < minx)
{
minx= Len[j];
k = j;
}
}
visit[k] = true; //找到,加入U
if (ans < minx) //保存最短路径中最大的一条边
{
ans = minx;
}
//i=1时,U中只有起点xx和新加入的k,Len[j]与prim[k][j]比较:就是比较xx到j的距离和新加入U的k顶点到j的距离
//之后,Len[j]就是U到j的最短距离啦,这样把U中所有顶点看成一个,Len[j]就是U到j(V-U中任意一个)的最短距离
//以此类推,i>1 时,每次都把原来的顶点集U到j的距离和新加入的k到j的距离比较,这样得到了新U到j的最短距离
//从而,就得到了新U到V-U中任一顶点的距离,保存在 Len中
for (int j = 1; j <= n; j++)
{
if ( !visit[j] && Len[j] > prim[k][j])
{
Len[j] = prim[k][j];
}
}
}
return ans;
}

public static void main(String[] args){
Scanner in=new Scanner(System.in);
int T=in.nextInt();
while((T--)>0){
int n=in.nextInt();
int[][] prim=new int[n+1][n+1];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j ++){
prim[i][j]=in.nextInt();
}
Main m=new Main(n,prim);
System.out.printf("%d\n",m.prim_solve(1)); //以第一个顶点开始,也可以是其他
}

}
}




解法二、kruskal算法
kruskal算法其实也是和prim算法一样求无向图的最小生成树,也属于贪心算法,不过prim算法的复杂度为O(n^2),适用于稠密图,而kruskal算法的复杂度为O(eloge),适用于稀疏图。

kruskal算法描述很容易理解,如下

1.设连通网N=(V,{E}),令最小生成树初始状态为只有n个顶点而无边的非连通图T=(V,{F}),每个顶点自成一个连通分量

2.在E中选取代价最小的边,加入到T中,若它的添加使T 中产生回路,则舍去此边,选取下一条代价最小的边

3.依此类推,直至T中有 n-1 条边为止

Kruskal算法牵涉到集合操作,包括集合的建立和集合的合并,这里用并查集解决。

初始化:把每个节点所在结合初始化为自身。
查找:查找元素所在的集合,即根节点
合并:将两个在不同集合的元素合并为一个集合,应将树深度小的合并到深度大的上面或将子孙少的合并到子孙多的上面。

import java.util.Scanner;   
import java.util.Arrays;
public class Main{
private int father[];
private int son[];
private Edge e[];
private int n;//结点个数
private int l;//边的数目

public Main(int n,int l,Edge[] e){
this.n=n;
this.l=l;
this.e=e;
father=new int[n];
son=new int[n];
for(int i = 0; i < n; ++i){
father[i] = i;//将每个顶点初始化为一个集合,父节点指向自己。
son[i]=1;//初始化每个父节点的儿子数为1
}
}


public int unionsearch(int x){ //查找根结点
return (x == father[x]) ? x : unionsearch(father[x]);
}

public boolean join(int x, int y){ //合并
int root1, root2;
root1 = unionsearch(x);
root2 = unionsearch(y);
if(root1 == root2){ //为环
return false;
}
else if(son[root1] >= son[root2]){
father[root2] = root1;
son[root1] += son[root2];
}
else
{
father[root1] = root2;
son[root2] += son[root1];
}
return true;
}


public int kruskal(){
int ans=0;
int ltotal=0;

Arrays.sort(e); //按权值由小到大排序
for(int i = 0; i < l; ++i)
{
if(join(e[i].a, e[i].b)==true)
{
ltotal++; //边数加1
ans= e[i].weight; //记录

}
if(ltotal == n - 1) //最小生成树条件:边数=顶点数-1
{

return ans;
}
}
return 0;
}


public static void main(String[] args){
Scanner in=new Scanner(System.in);
int temp=0;
int T=in.nextInt();
while((T--)>0){
int k=0;
int n=in.nextInt();
Edge[] e=new Edge[n*(n-1)/2];
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j ++){
if(i<j){//只读取上三角
e[k]=new Edge(i,j,in.nextInt());
k++;
}else{
temp=in.nextInt();
}

}
}
Main m=new Main(n,k,e);
System.out.printf("%d\n",m.kruskal());
}

}
}

class Edge implements Comparable

{
int a; //边的一个顶点,从数字0开始
int b; //边的另一个顶点
int weight; //权重

Edge(int a,int b,int weight){
this.a=a;
this.b=b;
this.weight=weight;
}

@Override
public int compareTo(Object o){
Edge m = (Edge)o;
int result=(int)(this.weight - m.weight);
if(result>0) return 1;
else if(result==0) return 0;
else return -1;
}

}

源码下载:
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值