例题:
并查集:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并
其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大
若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高
根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
洛谷P3367
先上代码:
关于并查集和路径压缩:
有a,b,c三个人
假设a和b打架了,a做了b的小弟。则令f[a]=b;
后来a打赢了c 黑社会
那么c就是a的小弟了。所以,令f[c]=a;
但是,c不知道b,这不符合要求。
所以,我们必须让c的大哥变成最大的老大。
int find(int k){ if(f[k]==k)return k; return find(f[k]); } f[c]=find(a);
这时,我们可以使途中经过的人的大哥也变成老大
//路径压缩 int find(int k){ if(f[k]==k)return k; return f[k]=find(f[k]); } f[c]=find(a);
而判定两个人的老大是否相等,只需用
if(find(a)==find(b))
即可
并查集支持的操作
并查集的数据结构记录了一组分离的动态集合S={S1,S2,…,Sk}。每个集合通过一个代表加以识别
代表即该元素中的某个元素,哪一个成员被选做代表是无所谓的,重要的是:如果求某一动态集合的代表两次
且在两次请求间不修改集合,则两次得到的答案应该是相同的。 动态集合中的每一元素是由一个对象来表示的,设x表示一个对象
并查集的实现需要支持如下操作: MAKE(x):建立一个新的集合,其仅有的成员(同时就是代表)是x。
由于各集合是分离的,要求x没有在其它集合中出现过。
UNIONN(x,y):将包含x和y的动态集合(例如Sx和Sy)合并为一个新的集合,假定在此操作前这两个集合是分离的。
结果的集合代表是Sx∪Sy的某个成员。一般来说,在不同的实现中通常都以Sx或者Sy的代表作为新集合的代表。
此后,由新的集合S代替了原来的Sx和Sy。 FIND(x):返回一个指向包含x的集合的代表
上代码:
#include<cstdio> #include<cstring> using namespace std; #define init for(int i=1;i<=n;i++) fa[i]=i const int maxn=2e4; int fa[maxn],n,m,x,y,z; inline int read() { int S(0); char c=getchar(); while(c>'9'||c<'0') c=getchar(); while(c>='0'&&c<='9') S=S*10+c-'0',c=getchar(); return S; } int find(int x){return fa[x]==x?fa[x]:fa[x]=find(fa[x]);} int main() { n=read(); m=read(); init; for(int i=1;i<=m;i++) { z=read(); if(z==1) { x=read(); y=read(); int a=find(fa[x]),b=find(fa[y]); if(a!=b) fa[a]=b; } else { x=read(); y=read(); if(find(fa[x])==find(fa[y])) printf("Y\n"); else printf("N\n"); } } return 0; }
求无向图的连通分量
这里提出来强调一下
因为求无向图连通分量是个非常常用的算法。
通过并查集可以使得空间上省去对边的保存,同时时间效率又是很高的。
需要特别指出的是,如果用链表来实现的话,最后任何在同一个集合(即连通块)中的元素,其代表指针的值都是相等的。
而采用有根树来实现的话,算法结束后,留下的依然是树的关系,因此如果希望每个元素都指向它的根的话
还需要对每个节点进行一次find操作,这样每个节点的父节点都是代表此集合的节点。在某些统计问题中,往往需要这样做
例题2:
洛谷P1195
题解:
#include<bits/stdc++.h> #define max 1005 using namespace std; int n,m,k,x,y,l,sum,ans; int t[max]; struct Edge { int u,v,w; }edge[max*10]; bool operator <(Edge a,Edge b) { return a.w<b.w; } int find(int x) { return t[x]==x?t[x]:t[x]=find(t[x]); } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) t[i]=i; for(int i=1;i<=m;i++) { scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); } sort(edge+1,edge+m+1); for(int i=1;i<=m;i++) { int fx=find(edge[i].u),fy=find(edge[i].v); if(fx!=fy) { t[fx]=fy; sum++; ans+=edge[i].w; } if(sum==n-k) { printf("%d",ans); return 0; } } puts("No Answer"); return 0; }