第一次写掘金,看看效果。
一、概念
顾名思义,合并、查找、集合,用于诠释集合之间的合并、查找。常用于图论算法和一些常规操作。
- 集合可以包含多个元素,如何表示一个集合?
把帮派来比喻集合,帮派的老大就是集合的代表元素。
- 如何确定两个相同元素属于同一个集合?
两个小弟的老大相同就是属于同一个帮派,也就是属于同一个集合。
- 如何表示集合的代表元素(即帮派的老大)?
用一个数组par表示集合,par[i]=x 表示i的老大是x,par[x]=y 表示x的老大是y,par[y]=y 表示y的老大是y,没有老大即帮主,也就是集合的代表元素。
二、方法
- 初始化:所有小弟都是独立的,自己是自己的老大,各个元素都是独立的集合。
static int[] par=new int[10086];
public static void init(int n){
for(int i=0;i<n;i++)
par[i]=i;
}
- 查找:找到集合的代表元素,也就是小弟找到帮主
public static int find(int x){
if(x==par[x])
return x;
return par[x]=find(par[x]);
}
- 例如有这么一个par数组,会递归去找到3作为集合的代表元素
| 下标i | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| par[i] | 1 | 2 | 3 | 3 |
- 路径压缩,
par[x]=find(par[x])这一句使得查找过的元素都直接指向老大,下次要找就不用慢慢搜索
| 下标i | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| par[i] | 3 | 3 | 3 | 3 |
- 合并:集合合并,两个帮派融合,一个帮派的老大认另一个帮派的老大为老大
public static void unite(int x,int y){
int xx=find(x);
int yy=find(y);
par[xx]=yy;
}
例如下面这个数组,有两个集合,老大分别是2和4
| 下标i | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| par[i] | 1 | 2 | 2 | 4 | 4 |
unite(0,3)合并之后变成
| 下标i | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| par[i] | 2 | 2 | 4 | 4 | 4 |
三、常见应用
- 国内城市要修铁路,给出组城市对,这两个城市之间要修铁路,最后有个询问,求问某两个城市是否连通。
- A和B是朋友,B和C是朋友,那么A和C也是朋友,给出好多好多组关系,求问最后有多少个朋友圈?
- 把老大当作树结构中的父节点,
par[i]=x表示i的父节点是x,如果知道树中每个节点的父节点,可以找到最近公共祖先。 - 提升例题:zoj3261
- 有n[0,]个星球,编号为i[0,n-1],每个星球有一个能量值 [0,]
- 一开始将某些星球连通,有m[0,20000]条通道
- 接下来表示开战后的操作,一边摧毁通道,一边查询。查询是指:该星球应该向哪个星球求救,求救的对象要求能量比自己大并且在连通的星球中能量最大,如果能量最大的星球有多个,则找编号小的
- 样例
Sample Input
2
10 20
1
0 1
5
query 0
query 1
destroy 0 1
query 0
query 1
Sample Output
1
-1
-1
-1
- 注意点:1.并查集没有断开操作,用末尾状态向初始状态递推(并查集逆向操作,保存连通路径),保存开战后的操作,离线处理 2.认能量大的星球作老大,能量相同找编号小的,需要在合并方法里加一些比较操作
- 题解
四、算法扩展
-
最小生成树 解题思路:保存图的所有边,按照边的权值从小到大排序,依次判断点是否在同一集合,如果不在,则连通,累计答案。
-
欧拉路径 (1)概念
- 欧拉路径:一条经过图G上每一条边(不是点)恰好一次的路径。(一笔画问题:一笔从头画到尾)
- 欧拉回路:起点和终点相同,是特殊的欧拉路径
(2)存在前提
- 必然是连通图
- 如果是无向图,图上 奇数度数的 点的个数 必须是0或者2
- 如果是有向图,则要么所有点的入度和出度一样(欧拉回路),要么有两个点的入度分别比出度多1和少1 (3)解题思路
- 用并查集判断是否连通+判断入度出度
- 用dfs,从头搜到尾不遗漏,判断走过的点数量和边数量