并查集学习

并查集学习

解释:

一个数组保留这个点所在在范围,通过一个唯一的值标识该点的范围,这个值为祖先节点pre。
判断范围时并查集会十分快。

步骤:

核心步骤在于维护这个范围数组pre[n],pre[i] = i 时表示,这个结点是这个范围的标识结点,不需要进行下一步寻找

初始化:

​ 初始化pre[n]数组,使得每个节点为一个独立的范围,pre[i] = i,当前祖先是本身

for(int i=1;i<=n;i++) pre[i] = i;

寻找:

​ 通过不断循环判断当前节点是否是标识节点,否则就寻找它的标识节点代表范围

int find(int x){
	int root = x;
	while(pre[root] != root) root = pre[root];
	pre[x] = root;
	return root;
}

优化
并查集路径压缩 | 菜鸟教程 (runoob.com)
​ 可以对它走过的路径进行优化,下次再次访问路径结点时直接找到根结点
​ 通过再次遍历一遍路径,并对路径的范围标识赋值

int find(int x) {
    // root存储根节点,p存储父节点,temp存储子节点
    int p = x,root = x,temp;
    while(pre[root] != root) {
        root = pre[root];
    }
    while(pre[p] != p) {
        temp = pre[p];
        pre[p] = root;
        p = temp;
    }
    return root;
}

合并

​ 将两个结点的范围定义为同一个,因此要将标识进行更新,对根结点进行更新
注意:合并结点时并没有对路径结点进行合并,因为只用寻找一次路径结点又会进行合并(省代码)

void merge(int x,int y){
    int rx = find(x); // 寻找x的范围结点标识,根节点
    int ry = find(y); // 寻找y的范围结点标识,根节点
    if(rx != ry){ // 范围不一致
        pre[rx] = ry; // rx结点并入ry范围,前后关系不影响思想的表达也可以pre[ry] = rx
    }
}

范围判断

if(find(x) != find(y)){
	// 题目要求执行逻辑
}

例题:P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

package 算法回顾.并查集.P3367_模板_并查集;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Scanner;

public class Main {
	/**
	 * 祖宗数组
	 */
	static int pre[];
	/**
	 * 快速输入(流标识对象(缓冲流读取对象(输入流读取对象(输入流))))
	 */
	static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
	/**
	 * 快速输出(输出对象(缓冲流输出对象(输出流输出对象(输出流))))
	 */
	static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
	
	/**
	 * 寻找祖先结点
	 * @param x
	 * @return 祖先节点
	 */
	static final int find(int x) {
		int p = x,root = x,temp;
		while(pre[root] != root) {
			root = pre[root];
		}
		while(pre[p] != p) {
			temp = pre[p];
			pre[p] = root;
			p = temp;
		}
		return root;
	}
	
	/**
	 * 输入封装
	 * @return 获取到的整数值
	 * @throws Exception
	 */
	static final int nextInt() throws Exception {
		st.nextToken();
		return (int) st.nval;
	}
	
	/**
	 * 范围合并
	 * @param x
	 * @param y
	 */
	static final void merge(int x,int y) {
		// 寻找祖先
		int fx = find(x);
		int fy = find(y);
		
		// 不在同一个范围
		if(fx != fy) {
			// 合并
			pre[fx] = fy;
		}
	}
	
	public static void main(String[] args) throws Exception {
		int n = nextInt(); 
		int m = nextInt();
		pre = new int[n+1];
		// 祖宗数组初始化
		for(int i=1;i<=n;i++) pre[i] = i;
		// 防止GC跟不上定义,定义放在逻辑外面
		int s,x,y;
		for(int i=1;i<=m;i++) {
			// int s,x,y;
			//输入数据
			s = nextInt();
			x = nextInt();
			y = nextInt();
			// 选择
			if(s == 1) {
				merge(x,y);
			}
			// 输出
			else pw.println(find(x) == find(y)?"Y":"N");
		}
		// 清空输出缓存区
		pw.flush();
	}
}
posted @ 2022-02-01 13:56  ?灰?  阅读(35)  评论(0)    收藏  举报