并查集的基本思想:将具有一定联系的节点,凑成一个集合
官方解释:并查集(Union-find Data Structure)是一种树型的数据结构。它的特点是由子结点找到父亲结点,用于处理一些不交集(Disjoint Sets)的合并及查询问题。
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
例题1:有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。(力扣:547)
// 使用并查集来解题:
// 什么是并查集简单的来说就是使用:按照一定的规则为连通在一起的节点选出一个公共的符父节点,
// 然后再寻找集合中有多少个父节点
class Solution {
public int findCircleNum(int[][] isConnected) {
int len=isConnected.length;
// 创建一个数组来保存当前节点的父节点
int[] parents=new int[len];
// 为每个节点初始化父节点
for(int i=0;i<len;i++){
parents[i]=i;
}
// 寻找由联系的节点为他们选出父节点
for(int i=0;i<len;i++){
for(int j=0;j<isConnected[0].length;j++){
if(isConnected[i][j]==1){
// 如果他们之间有联系,则将他们合并
union(parents,i,j);
}
}
}
// 不能使用set因为有的节点的父节点是没有指向代表节点,但它的父父节点指向了代表节点
// // 合并完后看看parent数组中的不同的值有多少个,这里我们可以使用set
// Set <Integer> set=new HashSet<Integer>();
// // 将parents中的值添加道set集合中去
// for(int i=0;i<len;i++){
// set.add(parents[i]);
// }
// // 返回结果
// return set.size();
int circles = 0;
for (int i = 0; i < len; i++) {
if (parents[i] == i) {
circles++;
}
}
return circles;
}
// 创建一个合并函数,规则是由我们自己定义的,例如我们可以让两个相连的节点的后一个节点作为父节点
public void union(int[] parents,int son,int far){
// 合并也得有个规则吧
// 否则就把两个节点的代表节点先找出来再进行处理
int s=find(parents,son);
int f=find(parents,far);
// 如果他们各自为自己的代表节点则将前面的节点并入后面的节点
if(s==son&&f==far){
parents[son]=parents[far];
return;
}
// 如果两个节点的代表节点是相同的无需做任何的处理直接返回即可
if(s==f){
return;
}
// 如果两个节点的代表节点不相同,将son的代表节点插入道far的代表节点中
if(s!=f){
parents[s]=parents[f];
return;
}
}
// 编写一个寻找父节点的方法
public int find(int[] parents,int target){
if(parents[target]==target){
return target;
}
int md=find(parents,parents[target]);
return md;
}
}
例题2: 树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。力扣(684)
这道题利用了并查集找出了重复的边:有两个有意思的点
1.首先如何判断两个节点之间的边时多余的?通过边将连个点进行和并时如果两个节点已经在同个集合中说明这条边时多余的
2.如何判断这是否是最新的一条边?这个我们不许要管我们直接使用后面出现的边来覆盖前面的边就行
// 上面使用了dfs的方法,下面使用并查集的方法进行解题
// 使用并查集的只要思想,根据一定的规则为连接在一起的节点选出一个父节点
// 并查集最主要的两部分时:第一:连接函数 第2:查找父节点函数
class Solution {
// 创建一个数组来保存所找到的多余的边
int[] result=new int[2];
public int[] findRedundantConnection(int[][] edges) {
int len=edges.length;
// 首先将每个节点作为自己的代表节点
int[] parents=new int[len+1];
for(int i=0;i<=len;i++){
parents[i]=i;
}
// 将相连在一起的节点通过一定的规则连接起来
for(int i=0;i<len;i++){
union(edges[i][0],edges[i][1],parents);
}
return result;
}
// 创建一个连接函数
// 我一般都是将前面的节点连接到后面的节点身上去
public void union(int node1,int node2,int[] parents){
// 首先判断连个节点的代表节点是否相同如果时一样的就说明
// 这两个节点已经处于同一个集合中
int pnode1=find(node1,parents);
int pnode2=find(node2,parents);
// 通过返回的代表节点对节点进行合并
// 首先如果两个节点的代表节点这说明,两个节点已经在同一个集合中,他们之间的边时多余的
if(pnode1==pnode2){
result[0]=node1;
result[1]=node2;
}else{
// 如果两个集合的代表节点不一样,这将前面的节点添加到后面的节点所在的集合
parents[pnode1]=pnode2;
}
}
// 创建一个查找代表节点的函数
// 这里查找的方法时使用递归
public int find(int target,int[] parents){
// 创建一个变量来保存代表节点
int md;
// 递归的出口
if(target==parents[target]){
return target;
}
md=find(parents[target],parents);
return md;
}
}
例题3:
题目链接:亲戚
import java.util.Scanner;
public class Main {
static int[] parents;
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();
int m=scan.nextInt();
int p=scan.nextInt();
// 创建一个数组保存节点之间的关系
int[][] relation=new int[m][2];
for(int i=0;i<m;i++) {
relation[i][0]=scan.nextInt();
relation[i][1]=scan.nextInt();
}
// 创建一个数组表示要查询的关系
int[][] find=new int[p][2];
for(int i=0;i<p;i++) {
find[i][0]=scan.nextInt();
find[i][1]=scan.nextInt();
}
// 创建一个数组保存每个节点的父节点
parents=new int[n+1];
// 初始化parents数组
for(int i=1;i<n+1;i++) {
parents[i]=i;
}
// 根据关系数组合并集合
for(int i=0;i<relation.length;i++) {
union(relation[i][0],relation[i][1]);
}
// 根据要查询的两人输出相应的关系
for(int i=0;i<p;i++) {
if(findfat(find[i][0])==findfat(find[i][1])) {
System.out.println("Yes");
}else {
System.out.println("No");
}
}
}
// 创建一个合并函数,将关系数组中的后一个人的parents指向前一个人
public static void union(int bre,int aft) {
// 先求出两个人的符节点
int brefa=findfat(bre);
int aftfa=findfat(aft);
// 如果他们各自为自己的父节点则让后面节点的父节点指向前一任
if(brefa==bre&&aft==aftfa) {
parents[aft]=bre;
return;
}
// 如果两个节点已经属于同一个集合,则不需要在进行处理
if(brefa==aftfa) {
return;
}
// 如果两个节点的代表节点不相同,将son的代表节点插入道far的代表节点中
if(brefa!=aftfa){
parents[aftfa]=parents[brefa];
return;
}
}
// 创建一个查询父节点的函数
public static int findfat(int temp) {
if(temp==parents[temp]) {
return temp;
}
return findfat(parents[temp]);
}
}