首页 > 其他分享 >并查集(路径压缩法+启发式合并法)

并查集(路径压缩法+启发式合并法)

时间:2024-08-16 16:31:12浏览次数:6  
标签:findfa 结点 路径 int 查集 rx fa 启发式 集合

我们从一道例题看起:洛谷P1551 亲戚

问题很简单,给出一个亲戚关系图,规定 \(x\) 和 \(y\) 是亲戚,\(y\) 和 \(z\) 是亲戚,那么 \(x\) 和 \(z\) 也是亲戚,那么 \(x\) 的亲戚都是 \(y\) 的亲戚,\(y\) 的亲戚也都是 \(x\) 的亲戚,再给定 \(P_i\) 和 \(P_j\),询问他们是否是亲戚,输出 YesNo

人数,亲戚关系,询问次数 \(\leq 5000\)。

这样的亲戚关系图我们可以把它看作若干个集合,一个人就是一个元素,得知两个人是亲戚后,将他们各属于的集合合并即可,查询 \(P_i\) 和 \(P_j\) 是否是亲戚,就只要查询他们在不在同一个集合中。

因为一个元素只可能属于一个集合,所以我们可以为每一个集合选取一个代表元。这样子做,我们查询两个元素是否属于同一个集合就只需要比较他们各自的集合的代表元是否相同即可。

我们尝试实现合并集合和查询集合代表元这两个操作,查询集合代表元可以用数组标记将时间复杂度优化成 \(O(1)\),而合并集合时需要改变其中一个集合中的所有元素的代表元,时间复杂度非常高,很明显需要优化。

这道题我们就可以使用并查集来解决,它的思路是,维护一个树形结构,对于同一个集合中的元素,把他们之间的关系构成一棵树,那么这棵树就有相同的根节点,我可以通过判断他们根节点是否相同,来判断他们是否处于同一个集合中。

初始化

由于初始时每一个集合都只有一个元素,所以把这些集合都单独看作一棵树。

以这道题的样例为例:

6 5 3
    
1 2
1 5
3 4
5 2
1 3
    
1 4
2 3
5 6

有 \(6\) 个人,也就是 \(6\) 个集合,\(6\) 棵树,每棵树的根是它自己。

写成代码就是:

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

合并

将两个集合合并,就是对两棵树合并,将其中一棵树的根结点变成另一棵树的根结点,如我们要合并 \((1,2)\) 和 \((1,5)\),如下图所示:

\(5\) 次操作完成后如下图所示:

写成代码也很简单:

int findfa(int x){//求根结点
    return (fa[x]==x)?x:(fa[x]=findfa(fa[x]));
}
......
if(findfa(x)!=findfa(y)){
    fa[findfa(x)]=findfa(y);
}

查询

还是对于上面那几棵树,查询 \((1,4)\) 时,因为 \(1\) 和 \(4\) 都属于同一棵树上,他们的根结点都是 \(4\)。

int merge(int x,int y){
	if(findfa(x)==findfa(y)) return true;
	else return false;
}

合并和查询的 findfa 函数都使用路径压缩法,因为我们不管在合并还是查询操作,都只关心每棵树的根结点,我们就干脆不记录结点的父亲,只记录该结点所属的这棵树的根结点,并更新沿途经过结点的父亲为根结点即可,findfa 函数还有一种非递归形式,如下:

int findfa(int x){
	int rx=x;
	while(fa[rx]!=rx){
        rx=fa[rx];
    }
	while(fa[x]!=rx){
		int t=fa[x];
		fa[x]=rx;
		x=t;
    }
	return rx;
}

这样子,我们每次操作的时间复杂度只有 \(O(\log n)\)(平均意义下)。

启发式合并

上面那种路径压缩法,每一次合并,我们都会把一棵树的根设为另一棵树的根,这样就会把树摞得很高很高,因此,我们可以把两棵树中树高小的那棵树的根结点设为另一棵树的根,这样,只有树高一样时,合并后的树高才是他们其中的一个树高加 \(1\)。

具体代码实现如下:

void init(){
	for(int i=1;i<=n;i++){
        fa[i]=i;
        h[i]=1;//初始树高
	}
}
void merge(int x,int y){
	int rx=findfa(x),ry=findfa(y);
	if(rx==ry){
        return ;
    }
	if(h[rx]>h[ry]){
        swap(x,y);
        swap(rx,ry); 
    }
	fa[rx]=ry;
	h[ry]=max(h[ry],h[rx]+1);
}

这样,相比于路径压缩法,启发式合并法的单次操作优化到 \(O(\log n)\)(稳定)。


当我们将路径压缩法和启发式合并法放在一起,单次操作的时间复杂度只有 \(O(\alpha(n))\),但是任何一道 OI 题均不会区分\(O(\alpha(n))\) 和 \(O(\log n)\),因此我们用任意一种方法均可。

该题的路径压缩法代码如下:

/*Written by smx*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e4+5;
int n,m,q;
int fa[MAXN];
void init(){
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
}
int findfa(int x){
	return (fa[x]==x)?x:(fa[x]=findfa(fa[x]));
}
int merge(int a,int b){
	if(findfa(a)==findfa(b)){
		return true;
	}else{
		return false;
	}
}
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>q;
	init();
	while(m--){
		int a,b;
		cin>>a>>b;
		fa[findfa(a)]=findfa(b);
	}
	while(q--){
		int a,b;
		cin>>a>>b;
		if(merge(a,b)){
			cout<<"Yes\n";
		}else{
			cout<<"No\n";
		}
	}
	return 0;
}

标签:findfa,结点,路径,int,查集,rx,fa,启发式,集合
From: https://www.cnblogs.com/shimingxin1007/p/18363102

相关文章

  • SLF4J: Class path contains multiple SLF4J bindings. 运行报错 表示在您的应用程序
    java使用SLF4J时出现下面的错误,是因为项目中使用了多个SLF4J的类库SLF4J:ClasspathcontainsmultipleSLF4Jbindings.SLF4J:Foundbindingin[jar:file:/D:/%e5%bd%93%e5%89%8d%e5%b7%a5%e4%bd%9c/SipPBX%e8%ae%af%e6%97%b6/JoinCallOMCC/JoinCallOMCC/out/artifacts/......
  • 【MATLAB源码-第138期】基于matlab的D2D蜂窝通信仿真,对比启发式算法,最优化算法和随机
    操作环境:MATLAB2022a1、算法描述D2D蜂窝通信介绍D2D蜂窝通信允许在同一蜂窝网络覆盖区域内的终端设备直接相互通信,而无需数据经过基站或网络核心部分转发。这种通信模式具有几个显著优点:首先,它可以显著降低通信延迟,因为数据传输路径更短;其次,由于减少了基站的中转,可以提高......
  • Java中的类加载机制与类路径管理
    Java中的类加载机制与类路径管理大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!一、Java类加载机制概述Java虚拟机(JVM)的类加载机制是Java语言的核心特性之一,它确保了Java程序的动态性和灵活性。类加载机制主要分为三个阶段:加载(Loading)、链接(Linking......
  • 并查集
    并查集(递归写法)#include<bits/stdc++.h>usingnamespacestd;constintX=10010;intf[X];intn,m; //初始化voidinit(){ for(inti=0;i<X;i++){ f[i]=i; }}//查找上级是谁intfind(intx){ if(x!=f[x]){ returnf[x]=find(f[x]);//路径......
  • 【知识】并查集的单点删除 &【题解】SP5150
    \(\mathfrak{1st.}\)前言-->题目传送门<--原先这道题的难度是紫,我觉得题目翻译看不懂,就去讨论版发了个贴,然后题管更新了题目翻译,并且把难度降到了蓝……\(\mathfrak{2nd.}\)思路赶时间或嫌啰嗦的可以直接跳到『思路归纳』。我们先从普通并查集开始思考对于删除单点\(x\),......
  • 动态规划-不同路径问题
    本篇是动态规划算法题目介绍的第二篇,如果对其他题目感兴趣的话,可以前往动态规划_Yuan_Source的博客-CSDN博客不同路径Ⅰ一个机器人位于一个 mxn 网格的左上角。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。问总共有多少条不同的路径?解题思路......
  • 基于Dijkstra、A*和动态规划的移动机器人路径规划(Matlab代码实现)
     ......
  • 欧拉路径
    借鉴文章\(1\).欧拉路径定义:图中经过所有边恰好一次的路径叫欧拉路径(也就是一笔画)。如果此路径的起点和终点相同,则称其为一条欧拉回路。\(2.\)欧拉路径判定(是否存在):有向图欧拉路径:图中恰好存在\(1\)个点出度比入度多\(1\)(这个点即为起点\(S\)),\(1\)个点入度比出度多......
  • python实现迷宫最佳路径规划
    在Python中实现迷宫路径的最佳路径规划,我们通常可以使用图搜索算法,如广度优先搜索(BFS)或更高效的A搜索算法。A算法因其结合了最佳优先搜索(如Dijkstra算法)和启发式信息(如曼哈顿距离或欧几里得距离)来评估节点的潜力,所以在寻找最短路径时非常有效。下面将展示如何使用A*算法在Pyth......
  • 关于并查集
    关于冰茶姬简述冰茶姬是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。顾名思义,冰茶姬支持两种操作:合并(Union):合并两个元素所属集合(合并对应的树)查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于......