并查集,带权并查集--从入门到入坑 完整版

狗头,通过入门题来入门并查集。
建议:让前一个数成为父结点

不带权并查集:

重要的是三个方法:初始化,查找,合并(按顺序的):

模块一

public static void init()
	{
		for(int i=1;i<=n;i++)
		{
			fa[i]=i;
		}
	}
public static int father(int i)
{
	if(i!=fa[i])//不能写成i!=father(i)   
	{
		return father(fa[i]);//优化递归可以改成:return fa[i]=father(fa[i])进行状态压缩实际上就是进行了,二叉树转成深度为一的多叉树
	}
	return i;
}
public static void merge(int x,int y) {
		int  fax=father(x);int fay=father(y);
		//不要用int  fax=fa[x];int fay=fa[y];因为有些fa[i]并没有更新自己的父结点
		if(fax!=fay)
		{
			fa[fay]=fax;
		}
	}

另外还有方法,更新根节点。因为有的fa[i]并不是根节点,要手动更新

for(int i=1;i<=n;i++)
		{
			fa[i]=father(i);
		}

模块二

构造成一个类,整洁,方便。

static class DSU{//并查集
		int root[];
		int size[];
		
		public DSU(int n)
		{
			root=new int[n];
			size=new int[n];
			for(int i=0;i<n;i++)
			{
				root[i]=i;
			}
			Arrays.fill(size, 1);
		}
		
		public int find(int x)
		{
			if(root[x]!=x)
			{
				root[x]=find(root[x]);
			}
			return root[x];
		}
		
		public void union(int x,int y)
		{
			int rootX=find(x);int rootY=find(y);
			if(rootX==rootY) return;
			else {
				if(size[rootX]<size[rootY])//
				{
					root[rootX]=rootY;
					size[rootY]++;//??
				}
				else {
					root[rootY]=rootX;
					size[rootX]++;//??
				}
			}
		}
	}

找朋友

在社交的过程中,通过朋友,也能认识新的朋友。在某个朋友关系图中,假定 A 和 B 是朋友,B 和 C 是朋友,那么 A 和 C 也会成为朋友。即,我们规定朋友的朋友也是朋友。

现在,已知若干对朋友关系,询问某两个人是不是朋友。

请编写一个程序来解决这个问题吧。

输入格式
第一行:三个整数 n,m,p(n≤5000,m≤5000,p≤5000)分别表示有n 个人,m 个朋友关系,询问p 对朋友关系。

接下来 m 行:每行两个数Ai,Bi1≤Ai,Bi≤N,表示Ai​ 和 Bi具有朋友关系。

接下来 p 行:每行两个数,询问两人是否为朋友。

输出格式
输出共 p 行,每行一个Yes或No。表示第i个询问的答案为是否朋友。

样例输入
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
样例输出
Yes
Yes
No

package test3;
import java.util.Scanner;
//2021年3月9日下午6:32:38
//writer:apple
public class bingchaji_ {
	static int fa[]=new int[1000];//fa[i]表示i的父节点
	static int a[]=new int[1000];
	static int n;

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		n=scanner.nextInt();
		int m=scanner.nextInt();
		int p=scanner.nextInt();
		init();
			
		for(int i=1;i<=m;i++)
		{
		   int x=scanner.nextInt();int y=scanner.nextInt();
//		   a[i*2-1]=x;a[i*2]=y;
		   merge(x, y);
		}
		for(int i=1;i<=p;i++)
		{
			int x=scanner.nextInt();int y=scanner.nextInt();
			if(father(x)==father(y) ) System.out.println("yes");//这个判断条件千万不能写fa[x]==fa[y],会出错,因为有些fa[i]并没有更新自己的父结点,用father(x)==father(y)则能找到最终的父结点
			else {
				System.out.println("no");
			}
		}
	
	}
	public static void init()
	{
		for(int i=1;i<=n;i++)
		{
			fa[i]=i;
		}
	}
	public static int father(int i)
	{
		if(i!=fa[i])
		{
			return fa[i]=father(fa[i]);
		}
		return i;
	}
	public static void merge(int x,int y) {
		int  fax=fa[x];int fay=fa[y];
		if(fax!=fay)
		{
			fa[fay]=fax;//不要写fa[y]=x;都直接在父结点做处理
		}
	}

}

城镇交通

某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?

Input
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。

Output
对每个测试用例,在1行里输出最少还需要建设的道路数目。

Sample Input

4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0

Sample Output

1
0
2
998

package test3;

import java.util.Scanner;

//2021年3月9日下午9:16:26
//writer:apple
public class bingchaji_2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		
		while(true)
		{
			int N=scanner.nextInt();
			if(N==0) break;
			else {
				int fa[]=new int[N+1];
				int m=scanner.nextInt();
				init(fa, N);
				while(m-->0)
				{
					int x=scanner.nextInt();int y=scanner.nextInt();
					merge(x, y, fa);
				}
				
				int ans=0;
				for(int  i=1;i<=N;i++)
				{
					if(getfa(i, fa)==i) ans++;
				}
				System.out.println(ans-1);
			}
		}

	}
	
	public static void init(int fa[],int n)
	{
		for(int i=1;i<=n;i++)
		{
			fa[i]=i;
		}
	}
	
	public static int  getfa(int i,int fa[])
	{
		if(fa[i]==i)
		{
			return i;
			}
		return fa[i]=getfa(fa[i], fa);
	}
	
	public static void merge(int x,int y,int fa[])
	{
		int fax=getfa(x, fa);int fay=getfa(y, fa);
		if(fax!=fay)
		{
			fa[fay]=fax;
		}
	}
}

Covid病毒

一些学生被分组,0号可能感染病毒,跟他同一集合的也可能感染,那么给出几个分组,问感染的人数。所以,0作为祖先节点,只要与0同集合就将人数数组增加。
Sample Input

100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0

Sample Output

4
1
1

AC:和0是同一组的则人数增加,每次让两个数据进行合并就行

package test3;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;

//2021年3月9日下午10:45:43
//writer:apple
public class bingchaji_3 {
	static int fa[]=new int[40000];
	static int n;
	static int m;
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		while(true)
		{
			Arrays.fill(fa, 0);
			n=scanner.nextInt();
			m=scanner.nextInt();
			init();
			if(n==0&&m==0) break;
			while(m-->0)
			{
				int c=scanner.nextInt();
				ArrayList<Integer> a=new ArrayList<>();
				for(int i=1;i<=c;i++)
				{
					a.add(scanner.nextInt());
				}
				
				for(int i=0;i<=a.size()-2;i++)
				{
					merge(a.get(i),a.get(i+1));
				}
			}
			int ans=1;
			for(int i=1;i<n;i++)
			{
				if(getfa(i)==getfa(0)) ans++;
			}
			System.out.println(ans);
		}
	}
	
	public static void init()
	{
		for(int i=1;i<=n-1;i++)
		{
			fa[i]=i;
		}
	}
	
	public static int getfa(int i)
	{
		if(fa[i]!=i) {
			return fa[i]=getfa(fa[i]);
		}
		return i;
	}
	
	public static void merge(int i,int j)
	{
		int fai=getfa(i);int faj=getfa(j);
		if(fai!=faj)
		{
			fa[faj]=fai;
		}
	}

}

带权并查集

核心代码:
在不带权的基础上,外加一个数组,用来存放结点之间的权值

public static void init(int n)
	{
		for(int i=1;i<=n;i++)
		{
			fa[i]=i;
		}
	}
public static int getfa(int i)
	{
		if(fa[i]!=i) 
		{
			int t=fa[i];//找到i的父结点		
			fa[i]=getfa(fa[i]);//要确认了fa[i]之后才能,进行score[i]+=score[t];  
			score[i]+=score[t];
		}
	return fa[i];
	}
public static void merge(int i,int j,int fenshu )
	{
		int fai=getfa(i);int faj=getfa(j);
		if(fai!=faj)
		{
			fa[fai]=faj;//让后数成为父结点
			score[fai]=score[j]+fenshu-score[i];//画j,faj    i,fai 两条边就能理解了
		}
	}

另外还有方法,更新根节点。因为有的fa[i]并不是根节点,要手动更新

for(int i=1;i<=n;i++)
		{
			fa[i]=father(i);//同时会更新所有score。
		}

例题:
HihoCoder-1515-分数调查

描述
小Hi的学校总共有N名学生,编号1-N。学校刚刚进行了一场全校的古诗文水平测验。

学校没有公布测验的成绩,所以小Hi只能得到一些小道消息,例如X号同学的分数比Y号同学的分数高S分。

小Hi想知道利用这些消息,能不能判断出某两位同学之间的分数高低?

输入
第一行包含三个整数N, M和Q。N表示学生总数,M表示小Hi知道消息的总数,Q表示小Hi想询问的数量。

以下M行每行三个整数,X, Y和S。表示X号同学的分数比Y号同学的分数高S分。

以下Q行每行两个整数,X和Y。表示小Hi想知道X号同学的分数比Y号同学的分数高几分。

对于50%的数据,1 <= N, M, Q <= 1000

对于100%的数据,1 <= N, M, Q<= 100000 1 <= X, Y <= N -1000 <= S <= 1000

数据保证没有矛盾。

输出
对于每个询问,如果不能判断出X比Y高几分输出-1。否则输出X比Y高的分数。

样例输入

10 5 3
1 2 10
2 3 10
4 5 -10
5 6 -10
2 5 10
1 10
1 5
3 5
样例输出

-1
20
0

package test3;
import java.util.Scanner;
//2021年3月10日上午10:17:22
//writer:apple
public class bingchaji_5 {
	static int fa[]=new int[100005];
	static int score[]=new int[100005];//score[i]表示 i号成绩比根节点高出的分数

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();
		int m=scanner.nextInt();
		int t=scanner.nextInt();
		init(n);
		while(m-->0)
		{
			int x=scanner.nextInt(); int y=scanner.nextInt();int fenshu=scanner.nextInt();
			merge(x, y, fenshu);
		}
		while(t-->0)
		{
			int x=scanner.nextInt();int y=scanner.nextInt();
			if(getfa(x)==getfa(y))//表示为同一集合
			{
				System.out.println(score[x]-score[y]);
			}
			else {
				System.out.println("-1");
			}
		}		
	}
	
	public static void init(int n)
	{
		for(int i=1;i<=n;i++)
		{
			fa[i]=i;
		}
	}
	
	public static int getfa(int i)
	{
		if(fa[i]!=i) 
		{
			int t=fa[i];//找到i的父结点
			
			fa[i]=getfa(fa[i]);//要确认了fa[i]之后才能,进行score[i]+=score[t];  
			score[i]+=score[t];
		}
	return fa[i];
	}
	
	public static void merge(int i,int j,int fenshu )
	{
		int fai=getfa(i);int faj=getfa(j);
		if(fai!=faj)
		{
			fa[fai]=faj;//让后数成为父结点
			score[fai]=score[j]+fenshu-score[i];//画j,faj    i,fai 两条边就能理解了
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值