图论之并查集一

一、概念

并查集主要用于解决元素分组的问题。

并指合并;查指查找;集指集合。通俗一点来说就是查找、合并集合。

对于两个不同的元素,当它们归属于一个父元素或者说拥有一个共同的祖先,我们就说这两个元素同属一个集合。

百度百科解释并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。

二、应用

并查集在图中的用途比较广泛。

  •  判定无向图中是否存在环
  •  合并集合
  • 判定元素是否同属一个集合,统计集合个数

三、解析

例一

假定有a、b、c、d四个元素,它们的关系如下图所示,现判断元素a、c是否同属一个集合?

观察发现,元素a、c属于同一个集合,怎么判断的?

通过图我们可以获取两个信息

a ——> b ——> d          c ——> d

 用树的知识解释:

a的父节点是b,b的父节点是d,d是a的祖先

c的父节点是d

元素a和元素c都有一个共同的父节点d,因此元素a、元素c同属一棵树/同属一个集合。 

例二

有这么一个新的元素关系,如下图所示,请问元素a、元素2是否同属一个集合?如若不是,请合并。

显然,元素a和元素2并不属于同一个集合。 那么,怎么合并呢?

我们知道,元素a的祖先是元素d,元素2的祖先是元素3,那么能否直接将元素d和元素3合并呢?

答案是肯定的。正确的操作就是将元素d挂靠到元素3下面(实际操作不会这么暴力,会进行相关优化)。

到了这里,小伙伴大概已经知道了,如何判断两个元素是否处于同一个集合/合并到同一个集合中。

解疑

大家也许会疑惑:集合哪里来的从属关系?

这就是并查集解题的巧妙之处了:在开头我们就解释了,并查集是一种树型的数据结构。

在我们利用并查集处理集合元素的过程中,选出集合中的某一个元素作为集合的代表(元素可以是集合中的任意一个),也就是其他的元素的共同祖先(树中的根节点)。这一步在初始化的代码就能体现出来,后面会讲。

四、代码

4.1 初始化

把每一个元素当做一个单独的集合,并设置父元素指向自己本身

int[] parent;

// n代表集合内元素的个数
public void init(int n){
    parent = new int[n];
    for (int i = 0; i < n; i++) {
        // 把每一个元素当做一个单独的集合,并设置父元素指向自己本身
        parent[i] = i;
    }
}

4.2 查

怎样才算找到了某一个元素的父元素?

在初始化时,我们将所有元素的父元素都定义为其本身: parent[i] = i

联系并查集处理集合的方法:选取集合中的一个元素作为代表元素,通俗的说就是选取一个元素作为其他的元素的祖先(最开始集合中只有一个元素)。

说明无论怎么合并,必定会存在:x == parent[x],而这就是递归的终止条件。

public int find(int x) {
    if (parent[x] == x) return x;
    int parentX = parent[x];
    return find(parentX);
}

思考:这个查找过程能不能进行优化?(联系我们查找的目的)

4.3 合并

合并前,必须先找到两个元素的祖先并判断是否已经在同一个集合。


/**
 * 合并
 * @param x 元素1
 * @param y 元素2
 * @return false:表示两元素已经在同一个集合,不用合并    true:合并成功
 */
public boolean merge(int x,int y) {
    // 1. 找父元素
    int parentX = find(x);
    int parentY = find(y);
    // 若两个元素的祖先相同,说明已经在同一个集合了
    if (parentX == parentY) return false;
    // 2. 合并
    // 这里两种写法,哪一种都可以
    parent[parentX] = parentY;
    // parent[parentY] = parentX;
    return true;
}

4.4 总代码

int[] parent;

/**
 * 1. 初始化
 */
public void init(int n){
    parent = new int[n];
    for (int i = 0; i < n; i++) {
        parent[i] = i;
    }
}

/**
 * 2. 查
 */
public int find(int x) {
    if (parent[x] == x) return x;
    int parentX = parent[x];
    return find(parentX);
}


/**
 * 3. 合并
 * @param x 元素1
 * @param y 元素2
 * @return false:表示两元素已经在同一个集合,不用合并    true:合并成功
 */
public boolean merge(int x,int y) {
    // 1. 找父元素
    int parentX = find(x);
    int parentY = find(y);
    // 若两个元素的祖先相同,说明已经在同一个集合了
    if (parentX == parentY) return false;
    // 2. 合并
    // 这里两种写法,哪一种都可以
    parent[parentX] = parentY;
    // parent[parentY] = parentX;
    return true;
}

五、总结

到这里,并查集的概念以及代码模板就讲解完成了,后续会继续讲解并查集的相关优化以及有关的算法题目。

希望大家看了我的文章有所收获!

如果有写得不好的地方,欢迎大家指正!

完结撒花!!!!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值