Skip to content
本页目录

并查集

本篇来手动实现JavaScript中没有的数据结构:并查集

定义节点

首先定义包装节点结构:

JavaScript
class Ele {
  constructor(ele) {
    this.ele = ele
  }
}

初始化

该数据结构会用到三个hashMap数据结构,为了方便描述,将值ele的包装节点new Ele(ele)记为nodenode的父元素记为nodeP

  • eMap:用于存放包装节点,即{ ele : node }
  • fMap:用于存放包装节点包装节点的父节点,即{ node : nodeP }
  • sMap:用于存放某个集合的头节点该集合大小,即{ node : number}

并查集是建立在数据初始化之上的,未被初始化的数据均可视为无效数据!!!

JavaScript
class UnionFindSet {
  constructor(elements) {
    this.eMap = new Map()
    this.fMap = new Map()
    this.sMap = new Map()
    // 初始化
    for (const ele of elements) {
      const node = new Ele(ele)
      this.eMap.set(ele, node)
      this.fMap.set(node, node)  // 父节点设为自己
      this.sMap.set(node, 1)
    }
  }
}

查找集合

JavaScript
class UnionFindSet {
  // 其它逻辑

  // 辅助函数
  findHead(node, path = []) {
    // 找到集合代表节点,即头节点
    while (node != this.fmap.get(node)) {
      path.push(node)
      node = this.fMap.get(node)
    }
    // 扁平化优化
    while (path.length) {
      this.fMap.set(path.pop(), node)
    }
    // 返回头节点
    return node
  }
  // 查找集合
  isSameSet(e1, e2) {
    if (this.eMap.has(e1) && this.eMap.has(e2)) {
      const [n1, n2] = [this.eMap.get(e1), this.eMap.get(e2)]
      return this.findHead(n1) == this.findHead(n2)
    }
    return false
  }
}

合并集合

JavaScript
class UnionFindSet {
  // 其它逻辑
  
  union(e1, e2) {
    // 只管初始化后的数据
    if (this.eMap.has(e1) && this.eMap.has(e2)) {
      const e1P = findHead(this.eMap.get(e1))
      const e2P = findHead(this.eMap.get(e2))
      if (e1P != e2P) {  // 头节点不一样,一定不是同一集合,故合并
        const [s1, s2] = [this.sMap.get(e1P), this.sMap.get(e2P)]
        const big = s1 >= s2 ? e1P : e2P
        const small = big == e1P ? e2P : e1P
        this.fMap.set(small, big)  // 将小集合挂在大集合的下面
        this.sMap.set(big, s1 + s2)  // 更新集合大小
        this.sMap.delete(small)  // 小集合已经被合并了,需删除
      }
    }
  }
}

完整代码

查看
JavaScript
class Ele {
  constructor(ele) {
    this.ele = ele
  }
}
class UnionFindSet {
  constructor(elements) {
    this.eMap = new Map()
    this.fMap = new Map()
    this.sMap = new Map()
    // 初始化
    for (const ele of elements) {
      const node = new Ele(ele)
      this.eMap.set(ele, node)
      this.fMap.set(node, node)  // 父节点设为自己
      this.sMap.set(node, 1)
    }
  }
  // 辅助函数
  findHead(node, path = []) {
    // 找到集合代表节点,即头节点
    while (node != this.fmap.get(node)) {
      path.push(node)
      node = this.fMap.get(node)
    }
    // 扁平化优化
    while (path.length) {
      this.fMap.set(path.pop(), node)
    }
    // 返回头节点
    return node
  }
  // 查找集合
  isSameSet(e1, e2) {
    if (this.eMap.has(e1) && this.eMap.has(e2)) {
      const [n1, n2] = [this.eMap.get(e1), this.eMap.get(e2)]
      return this.findHead(n1) == this.findHead(n2)
    }
    return false
  }

  // 合并集合
  union(e1, e2) {
    // 只管初始化后的数据
    if (this.eMap.has(e1) && this.eMap.has(e2)) {
      const e1P = findHead(this.eMap.get(e1))
      const e2P = findHead(this.eMap.get(e2))
      if (e1P != e2P) {  // 头节点不一样,一定不是同一集合,故合并
        const [s1, s2] = [this.sMap.get(e1P), this.sMap.get(e2P)]
        const big = s1 >= s2 ? e1P : e2P
        const small = big == e1P ? e2P : e1P
        this.fMap.set(small, big)  // 将小集合挂在大集合的下面
        this.sMap.set(big, s1 + s2)  // 更新集合大小
        this.sMap.delete(small)  // 小集合已经被合并了,需删除
      }
    }
  }
}

数据结构应用

leetcode
  1. 岛屿问题系列
  2. 407. 接雨水 II