[路飞]_并查集适合解决的算法问题

946 阅读2分钟

什么是并查集

并查集基础

傻子都能看懂的并查集入门

并查集(Union-find Sets)是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。

并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。

基本操作

  1. 初始化建立一个新的并查集,其中包含 n 个单元素集合
  2. unite(a, b) 把两个元素合并
  3. findSet(index) 找到某个元素所在的集合

路径压缩

为了加快查找速度,查找时将x到根节点路径上的所有点的parent设为根节点,该优化方法称为压缩路径。

使用该优化后,平均复杂度可视为Ackerman函数的反函数,实际应用中可粗略认为其是一个常数。

代码实现并查集

class UnionFind {
  constructor(n) {
    // 每个节点初始化父节点为本身
    this.parent = new Array(n).fill(0).map((item, index) => index)
    // 每个节点上集合的数量,初始化1个本身
    this.rank = new Array(n).fill(1)
    // 几个个数,初始化为n
    this.setCount = n
  }
  // 快速查找
  findSet(index) {
    if (this.parent[index] !== index) {
      return this.findSet(this.parent[index])
    }
    return this.parent[index]
  }
  // 合并
  unite(a, b) {
    let root1 = this.findSet(a),
      root2 = this.findSet(b)
    if (root1 !== root2) {
      if (this.rank[root1] < this.rank[root2]) {
        // 交换
        [root1, root2] = [root2, root1]
      }
      // 小的合并到大的上
      this.parent[root2] = root1
      // 增加节点上集合数量
      this.rank[root1] += this.rank[root2]
      //总集合数量减少
      this.setCount--
    }
  }
  // 返回集合数量
  get count() {
    return this.setCount
  }
  // 两个节点是否连通
  connected(a, b) {
    let root1 = this.findSet(a),
      root2 = this.findSet(b)
    return root1 === root2
  }
}

算法题中的应用

547. 省份数量

/**
 * @param {number[][]} isConnected
 * @return {number}
 */
var findCircleNum = function(isConnected) {
  var n = isConnected.length
  var set = new UnionFind(n) // 新建并查集
  for(var i = 0; i < n; i++) {
    for(var j = 0; j < n; j++) {
      var tmp = isConnected[i][j]
      if(i !== j && tmp === 1) set.unite(i, j)
    }
  }
  return set.count // 返回集合的数量
};

684. 冗余连接

/**
 * @param {number[][]} edges
 * @return {number[]}
 */
var findRedundantConnection = function(edges) {
  var n = edges.length
  var union = new UnionFind(n) // 新建并查集
  var ans = null
  for(var i = 0; i < n; i++) {
    var tmp = edges[i]
    var node1 = tmp[0], node2 = tmp[1]
    // 如果边的两个节点都已经在同一个集合中则可以删除当前的边
    if(union.findSet(node1) === union.findSet(node2)) ans = tmp
    // 合并两个节点
    union.unite(node1, node2)
  }
  return ans
};