并查集(图论)

本文介绍了并查集的概念及其在图论中的应用,通过交友关系的例子阐述了合并集合与查找代表元的操作。并查集用于判断图的连通性,如在K算法求最小生成树的过程中,通过判断边的两个端点是否属于同一集合来避免形成环。文中还给出了测试案例及输出结果。
摘要由CSDN通过智能技术生成

并查集包括两个操作:合并集合(union)查找代表元(find)

参考大神的帖子

简单来说呢,就是找朋友、划分朋友圈的过程。比如现在有100个人,我们已知一些人是朋友关系,例如已知张三和赵四是朋友,赵四和王五是朋友,那么朋友的朋友也是朋友,自然而然,张三和王五也是朋友了。有些人是不管拖了多少中间人都不可能建立朋友关系的人,那就不是一个朋友圈的。

怎么判断两个人是不是一个朋友圈的呢?
我们可以在一个朋友圈里推选出一个交际花,大家都认识他,只要两个谈话,person1:我认识那谁谁!person2:哎呀,那么巧,我也认识那谁谁。鉴定完毕:你们就是一个圈的。

那么100个人分朋友圈的过程就可以归成两步。通过已知的朋友关系,合并集合,已知person1,person2认识,那就union(person1,person2)。
如果person1,person2认识,person2,person3认识,这时已经union(person1,person2)和union(person2,person3),再出现person1,person3认识,还需要重复合并吗?当然没有必要,这时查找代表元就起作用了,对于已合并到一个集合的,我们都知道自己的代表元,如果我们查找两个人的代表元一样,说明两人之间已有一条连线(可以建立朋友关系),就不用再重复加入了。第二个操作就是,在加入集合之前查找代表元,判断是不是已建立关系,不用再重复建立了

我觉得最能体现并查集思想的是,图的连通性。比如:K算法求最小主树。
K算法求最小主树的过程是:先将边集按从大到小排序,逐个加入边,如果加入的此边不会形成环,则加入此边,否则不加入。
那么我们就可以用并查集解决它。对于要加入的这条边,我们查询这条边的两个点是否为同一个代表元,是的话,就说明,这两个点已在一个集合,相互之间已经能够连接到达,那么再加入一条两个点之间的边,就会形成环。
如果两个点不是同一个代表元,就可以合并入最小主树集。

细节解释在代码中注释。

import java.util.Scanner;
public class Main
{
 	static class Edge
 	{
  		int from;
  		int to;
  		int weigth;
  		public Edge(int from, int to, int weigth)
  		{
   			this.from = from;
   			this.to = to;
   			this.weigth = weigth;
  		}
 	}
 	public static void main(String args[])
 	{
  		//测试
  		Scanner sc = new Scanner(System.in);
  		int N = sc.nextInt(); //节点个数
  		int M = sc.nextInt(); //边个数
  		Edge[] edges = new Edge[M];
  		for(int i=0;i<M;i++)
  		{
   			int f = sc.nextInt();
   			int t = sc.nextInt();
   			int w = sc.nextInt();
   			edges[i] = new Edge(f,t,w);
  		}
  		//这里其实应该加一个排序操作,按边的权值大小对边排序,为了方便,我就把输入改成了就是排好序的边
  		int ans = minTree(edges,N);
  		System.out.println(ans);
 	}
 	static int minTree(Edge[] edges,int N) //edges为边集,N是节点个数
 	{
  		int sum=0; //主树的最大权值
  
  		int[] parent = new int[N+1]; //记录每个点的代表元
  		for(int i=1;i<=N;i++)
  		{
   			parent[i]=i; //初始情况下,每个点都以自己为代表元,自己就是一个集合
  		}
  		for(int i=0;i<edges.length;i++) //逐个边加入,一条边一条边并入集合
  		{
   			int f = edges[i].from;
   			int t = edges[i].to;
   			//查代表元
   			int root1 = find(parent,f);
   			int root2 = find(parent,t);
   			if(root1!=root2) //代表元不同,则可并入
   			{
    				parent[root2]=root1; //一个代表元并入另一个之下,两个集合也就合并了
    				//说明这条边加入了最小主树集合,那就加上这条边的权值
    				sum+=edges[i].weigth;
    				//为了输出能看出包括了哪几个边,这里输出一下并入了最小主树的边
    				System.out.println(f+" -> "+t+" : "+edges[i].weigth);
   			}
  		}
  
  		return sum; //返回
 	}
 	static int find(int parent[],int node)
 	{
  		int index = node;
  		while(node!=parent[node])
  		{
   			node = parent[node]; //不停的往上级找代表元
  		}
  		while(index!=node) //将此节点找代表元过程中经过的点,压缩,归入代表元的下一级直接管理
  		{
   			int temp = parent[index];
   			parent[index]=node; //经过的点的父级直接改为代表元
   			index = temp;
  		}
  		return node;
 	}
}

测试输入:
6
7
1 2 9
2 3 7
1 3 6
3 5 5
4 5 5
1 5 3
3 6 2
测试输出:
1 -> 2 : 9
2 -> 3 : 7
3 -> 5 : 5
4 -> 5 : 5
3 -> 6 : 2
28

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值