并查集路径压缩_手把手带你学并查集

手把手带你学并查集

1 并查集的定义

并查集是一种维护集合的数据结构。它的名字“并”、“查”、“集”分别取自Union(合并)、Find(查找)和Set(集合)这三个单词。其中Union指合并两个集合的操作,Find指查找两个元素是否属于同一集合的操作。

应用场景:并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。比如说有若干个家族,每个家族人员都很庞大,要判断任意两个人是否有亲戚关系,确实不容易,这个时候就可以运用并查集,按照这些家族中已有的人员关系逐渐建立关系网,建立关系网后就能判断任意给出的两个人是否具有亲戚关系。

a0529628a2742279091898b40580b1a9.png

实现过程:并查集是如何实现的呢?其实就是用一个数组:int parent[N]。其中parent[i]表示元素i的父亲节点,而父亲节点本身也是这个集合内的元素(0<=i<=N-1)。例如parent[2]=1就表示元素2的父亲节点为1,特别地parent[i]=i表示元素i是该集合的根节点。8b30233693c010b28b85a5266f9d90a4.png我们来用并查集数组表示上图各节点之间的关系:

parent[0]=0;

parent[1]=1; parent[2]=1; parent[3]=2; parent[4]=2;

parent[5]=5;parent[6]=6

2 并查集的基本操作

(1)初始化

一开始,每个元素都是独立的集合,因此需要令每个parent[i]=i;

public UF(int count){
    this.count=count;
    parent=new int[count];
    for(int i=0;i        parent[i]=i;//先默认每个节点的父节点是自己
    }
}

(2)查找

同一个集合中只存在一个根节点,因此查找操作就是对给定的节点寻找其根节点的过程。

private int find(int x){
    while(parent[x]!=x){//如果不是根节点,继续循环
        x=parent[x];//获得自己的父节点
    }
    return x;
}

(3)合并

合并就是指把两个集合合并成一个集合。

private void union(int p,int q){
    int rootP=find(p);//查找p节点的根节点,记为rootP
    int rootQ=find(q);//查找q节点的根节点,记为rootQ
    if(rootP==rootQ)return;   
    parent[rootP]=rootQ;
}

3 路径压缩

为什么进行路径压缩?上面讲解的并查集查找函数是没有经过优化的,在极端条件下效率较低。现在考虑一种情况,即题目中给出的元素数量很多并且形成一条链,那么查找函数的效率会非常低。如图,总共有10^5个元素形成一条链,那么假设要进行10^5次查询,且每次查询都查询最后面的节点的根节点,那么每次都要花费10^5的计算量查找。

c65035100826c2c1cafa56094f37ad2d.png如何来优化呢?先看一个例子, parent[1]=1;

parent[2]=1;

parent[3]=2;

parent[4]=3;

其实它等价于

parent[1]=1;

parent[2]=1;

parent[3]=1;

parent[4]=1;

9b936e0c88d8d2b75df7c8caefa81112.png我们可以按照这个思路,加快查找根节点的速度,这就是所谓的路径压缩。

private int find(int x){
    //在查找过程中把路径上的各个节点的父节点设置为根节点
    if(x==parent[x])return x;
    //找到根节点
    int fx = find(parent[x]);
    parent[x] = fx;
    return fx;
}

分析完之后,我们一起来看看完整代码怎么写

class UF{
    int count;
    int[] parent;
    public UF(int count){
        this.count=count;
        parent=new int[count];
        for(int i=0;i            parent[i]=i;
        }
    }
    private int find(int x){
        if(x==parent[x])return x;
        int fx = find(parent[x]);
        parent[x] = fx;
        return fx;
    }
    private void union(int p,int q){
        int rootP=find(p);
        int rootQ=find(q);
        if(rootP==rootQ)return;
        else parent[rootP]=rootQ;
    }
    private boolean connect(int p,int q){
        int rootP=find(p);
        int rootQ=find(q);
        return rootP==rootQ;
    }
    private int count(){
        return this.count;
    }
}

4 一起来A个题目

结合一个题目来看看如何运用并查集解决问题。

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

输入:[[1,1,0], [1,1,0], [0,0,1]] 输出:2

解释:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。第2个学生自己在一个朋友圈。所以返回 2 。

输入:[[1,1,0], [1,1,1], [0,1,1]] 输出:1

解释:已知学生 0 和学生 1 互为朋友,学生 1 和学生 2 互为朋友,所以学生 0 和学生 2 也是朋友,所以他们三个在一个朋友圈,返回 1 。

class Solution{
    //内部并查集类,一般是固定写法
    class UF{
      int count;
      int[] parent;
      public UF(int count){
          this.count=count;
          parent=new int[count];
          for(int i=0;i              parent[i]=i;
          }
      }
      private int find(int x){
          if(x==parent[x])return x;
          int fx = find(parent[x]);
          parent[x] = fx;
          return fx;
      }
      private void union(int p,int q){
          int rootP=find(p);
          int rootQ=find(q);
          if(rootP==rootQ)return;
          else parent[rootP]=rootQ;
      }
      private boolean connect(int p,int q){
          int rootP=find(p);
          int rootQ=find(q);
          return rootP==rootQ;
      }
      private int count(){
          return this.count;
      }
  }

  public int findCircleNum(int[][] M) {
      int n=M.length;
      if(n==0)return 0;
      UF uf=new UF(n);
      for(int i=0;i          for(int j=i+1;j              if(M[i][j]==1){
              //两个学生有关系,则进行链接
                  uf.union(i,j);
              }
          }
      }
      return uf.count();
  }
}

5 题目链接

LeetCode547 朋友圈:https://leetcode-cn.com/problems/friend-circles/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值