萌新学习算法——并查集基础

并查集


在算法设计中,将一个集合和另外一个集合合并时,就会用到并查集。假如不用并查集,你可能会用到集合和列表来实现,这样会使代码看起来很复杂,而且执行效率不高,下面用洛谷的题目P3367 并查集.来举例子:

题目描述:

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式
第一行包含两个整数N、M,表示共有N个元素和M个操作。

接下来M行,每行包含三个整数Zi、Xi、Yi

当Zi=1时,将Xi与Yi所在的集合合并

当Zi=2时,输出Xi与Yi是否在同一集合内,是的话输出Y;否则话输出N

输出格式
如上,对于每一个Zi=2的操作,都有一行输出,每行包含一个大写字母,为Y或者N

代码(用集合和列表实现)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;

public class Main {
	public static int n, m;
	public static boolean flag[];  //用来判断是否已经加入列表,初始状态false
	//利用ArrayList来存储不同的集合,用set来存储每个集合里面的元素
	public static ArrayList<HashSet<Integer>> als = new ArrayList<>(); 

	public static void union(int x, int y) {
		if(x==y) return ;
		if (!flag[x] && !flag[y]) { //x和y都没有在列表里面,需要将x和y加入列表,并保存到同一个集合
			HashSet<Integer> newset = new HashSet<>();
			newset.add(x);
			newset.add(y);
			flag[x] = true;
			flag[y] = true;
			als.add(newset);
		} else {
			if (flag[x] && flag[y]) {  //当x和y都在列表里面
				int s = 0, e = 0;
				//找出x所在列表的下标
				for (int i = 0; i < als.size(); i++)
					if (als.get(i).contains(x)) { 
						s = i;
						break;
					}
				//找出y所在列表的下标
				for (int i = 0; i < als.size(); i++) {
					if (als.get(i).contains(y)) {
						e = i;
						break;
					}
				}
				//若x和y的下标相同,则证明在同一个集合,不需要合并
				if(e==s)
					return;
				//利用迭代器来实现,对下标大的那个集合遍历,将元素逐一加入下标小的那个集合中
				if (e < s) {	
					Iterator<Integer> set = als.get(s).iterator();
					while (set.hasNext())
						als.get(e).add(set.next());
					//最后将大的那个集合从列表中移除
					als.remove(s);
				} else {
					Iterator<Integer> set = als.get(e).iterator();
					while (set.hasNext())
						als.get(s).add(set.next());
					//最后将大的那个集合从列表中移除
					als.remove(e);
				}
				
			}//若只有一个在列表里面,另外一个没有在列表里面 
			else if (flag[x]) { //若x在列表中				
				int i;
				//找出x所在集合的下标
				for (i = 0; i < als.size(); i++)
					if (als.get(i).contains(x))
						break;
				//将y加入x所在集合
				als.get(i).add(y);
				//并将y加入列表
				flag[y] = true;
			} else {//若y在列表中	
				int i;
				//找出y所在集合的下标
				for (i = 0; i < als.size(); i++)
					if (als.get(i).contains(y))
						break;
				//将x加入y所在集合
				als.get(i).add(x);
				//并将y加入列表
				flag[x] = true;
			}
		}
	}

	//判断是否在同一个集合
	public static boolean inSet(int x, int y) {
		for (int i = 0; i < als.size(); i++)
			if (als.get(i).contains(x)) {
				if(als.get(i).contains(y)) //若x和y在同一个集合里面,则返回true,否则返回false
					return true;
				else
					return false;
				}
		return true;
	}

	public static void main(String[] args) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		String str[] = reader.readLine().split(" ");
		n = Integer.parseInt(str[0]);
		m = Integer.parseInt(str[1]);
		flag = new boolean[n + 1];
		int x, y, z;
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < m; i++) {
			str = reader.readLine().split(" ");
			z = Integer.parseInt(str[0]);
			x = Integer.parseInt(str[1]);
			y = Integer.parseInt(str[2]);
			if (z == 1)   
				union(x, y); 
			else {
				//若x==y 不用集合判断,若x或y不在列表里面,证明它自己作为一个集合
				//只有当flag[x] && flag[y] && inSet(x, y)为真时,才证明是同一个集合
				if (x==y||(flag[x] && flag[y] && inSet(x, y))) 
					sb.append("Y").append("\n");
				else
					sb.append("N").append("\n");
			}
		}
		System.out.println(sb);

	}
}

通过上面的代码,我们会发现特别麻烦,接下来我们引入并查集:这里介绍如何用它实现集合的合并和判断是否在同一个集合:

判断是否在同一个集合:

在这里我们先定义了一个find方法去查找x所在的集合的根节点,思想:保存每个集合的根节点,初始化为为-1(利用小于0来判读是否为根节点,后面的数值用来保存该集合有多少个点),其实就一个树状图,利用x集合保存的时它的父亲节点的父亲的下标,直到出现负数为根节点,再根据根节点判断是否为同一个集合,若根节点相同,则是同一个集合:

代码实现(未压缩的find方法):

public static int find(int x)
	{
		while(data[x]<0) return x;
		return find(data[x]);
	}

在这里有一个提高效率的方法,我们可以在找的同时顺带保存根节点,当下次查找的时候直接找到根节点,不需要每次都递归去寻找。

代码实现(压缩的find方法):

	public static int find(int x)
	{
		while(data[x]<0) return x;
		return data[x]=find(data[x]);
	}

接下来我们就可以利用find函数来实现查找两个数是否属于同一个集合。

work方法:

public static boolean work(int x,int y)
	{
		int a=find(x),b=find(y);
		if(a==b)
			return true;
		return false;		
	}

最后我们利用find方法来实现合并的union方法。

union方法:

	public static void union(int x,int y)
	{
		int a=find(x),b=find(y);
		if(a==b)
			return ;
		data[a]+=data[b];
		data[b]=a;
	}

完整代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
	public static int n,m,z,x,y;
	public static int data[];
	
	public static int find(int x)
	{
		if(data[x]<0) return x;	
		return data[x]=find(data[x]); //返回的是每个元素的根节点
	}
	
	public static void union()
	{
		int a=find(x);
		int b=find(y);
		if(a==b) return ; //若同为一个根,则不需要合并
		data[a]+=data[b];  //因为同为负数,所以可以直接相加
		data[b]=a; //将a作为b的根节点 
	}
	
	public static void main(String[] args) throws IOException {
		BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
		String str[]=reader.readLine().split(" ");
		n=Integer.parseInt(str[0]); m=Integer.parseInt(str[1]);
		data=new int[n+1];
		for(int i=1;i<=n;i++)
			data[i]=-1;
		StringBuffer sb=new StringBuffer();
		for(int i=0;i<m;i++)
		{
			str=reader.readLine().split(" ");
			z=Integer.parseInt(str[0]); x=Integer.parseInt(str[1]);
			y=Integer.parseInt(str[2]);
			if(z==1)
				union();
			else
			{
				if(find(x)==find(y))
					sb.append("Y").append("\n");
				else
					sb.append("N").append("\n");
			}
			
		}
		System.out.println(sb);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值