并查集

182 阅读4分钟

第一次写掘金,看看效果。

一、概念

顾名思义,合并、查找、集合,用于诠释集合之间的合并、查找。常用于图论算法和一些常规操作。

  1. 集合可以包含多个元素,如何表示一个集合?

把帮派来比喻集合,帮派的老大就是集合的代表元素。

  1. 如何确定两个相同元素属于同一个集合?

两个小弟的老大相同就是属于同一个帮派,也就是属于同一个集合。

  1. 如何表示集合的代表元素(即帮派的老大)?

用一个数组par表示集合,par[i]=x 表示i的老大是x,par[x]=y 表示x的老大是y,par[y]=y 表示y的老大是y,没有老大即帮主,也就是集合的代表元素。

二、方法

  1. 初始化:所有小弟都是独立的,自己是自己的老大,各个元素都是独立的集合。
    static int[] par=new int[10086];
    public static void init(int n){
        for(int i=0;i<n;i++)
            par[i]=i;
    }
  1. 查找:找到集合的代表元素,也就是小弟找到帮主
    public static int find(int x){
        if(x==par[x])
            return x;
        return par[x]=find(par[x]);
    }
  • 例如有这么一个par数组,会递归去找到3作为集合的代表元素
下标i0123
par[i]1233
  • 路径压缩par[x]=find(par[x])这一句使得查找过的元素都直接指向老大,下次要找就不用慢慢搜索
下标i0123
par[i]3333
  1. 合并:集合合并,两个帮派融合,一个帮派的老大认另一个帮派的老大为老大
    public static void unite(int x,int y){
        int xx=find(x);
        int yy=find(y);
        par[xx]=yy;
    }

例如下面这个数组,有两个集合,老大分别是2和4

下标i01234
par[i]12244

unite(0,3)合并之后变成

下标i01234
par[i]22444

三、常见应用

  1. 国内城市要修铁路,给出10510^5组城市对,这两个城市之间要修铁路,最后有10510^5个询问,求问某两个城市是否连通。
  2. A和B是朋友,B和C是朋友,那么A和C也是朋友,给出好多好多组关系,求问最后有多少个朋友圈?
  3. 把老大当作树结构中的父节点,par[i]=x表示i的父节点是x,如果知道树中每个节点的父节点,可以找到最近公共祖先。
  4. 提升例题:zoj3261
  • 有n\in[0,10410^4]个星球,编号为i\in[0,n-1],每个星球有一个能量值pip_i \in [0,10910^9]
  • 一开始将某些星球连通,有m\in[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. 最小生成树 解题思路:保存图的所有边,按照边的权值从小到大排序,依次判断点是否在同一集合,如果不在,则连通,累计答案。

  2. 欧拉路径 (1)概念

  • 欧拉路径:一条经过图G上每一条边(不是点)恰好一次的路径。(一笔画问题:一笔从头画到尾)
  • 欧拉回路:起点和终点相同,是特殊的欧拉路径

(2)存在前提

  • 必然是连通图
  • 如果是无向图,图上 奇数度数的 点的个数 必须是0或者2
  • 如果是有向图,则要么所有点的入度和出度一样(欧拉回路),要么有两个点的入度分别比出度多1和少1 (3)解题思路
  • 用并查集判断是否连通+判断入度出度
  • 用dfs,从头搜到尾不遗漏,判断走过的点数量和边数量