java实现并查集

介绍

并查集是一种特殊的树结构,示例图如下

可以很方便的进行以下两种操作:以上图为例

  • 判断元素6和元素4是否属于同一组,
  • 合并元素6和元素4所在的组

代码实现

public interface UF {

  /**
   * 容量
   */
  int size();

  /**
   * 是否已连接
   */
  boolean connected(int p, int q);

  /**
   * 合并
   */
  void union(int p, int q);

}

定义接口

public class UnionFind implements UF {

  private Node[] data;

  public UnionFind(int size) {
    data = new Node[size];
    for (int i = 0; i < data.length; i++) {
      //默认每个节点都指向自己
      data[i] = new Node(i);
    }
  }

  @Override
  public boolean connected(int p, int q) {
    rangeCheck(p, q);
    return root(p) == root(q);
  }

  /**
   * 查询p指向的根节点
   */
  private int root(int p) {
    int cur = p;
    while (true) {
      int parent = data[cur].parent;
      if (parent == cur) {
        return cur;
      }
      cur = parent;
    }
  }

  @Override
  public void union(int p, int q) {
    rangeCheck(p, q);
    int pRoot = root(p);
    int qRoot = root(q);
    if (pRoot == qRoot) {
      return;
    }
    data[qRoot].parent = pRoot;
  }

  public int size() {
    return data.length;
  }

  private void rangeCheck(int p, int q) {
    if (p < 0 || p >= size() || q < 0 || q >= size()) {
      throw new IllegalArgumentException("index is illegal");
    }
  }

  private static class Node {

    int parent;

    Node(int parent) {
      this.parent = parent;
    }
  }
}

测试代码

public class Main {

  public static void main(String[] args) {
    UF uf = new UnionFind(10);
    uf.union(4, 3);
    uf.union(3, 8);
    uf.union(6, 5);
    uf.union(9, 4);
    uf.union(2, 1);
    uf.union(5, 0);
    uf.union(7, 2);
    uf.union(6, 2);
    System.out.println(uf.connected(0, 2));
    System.out.println(uf.connected(0, 7));
    System.out.println(uf.connected(3, 9));
    System.out.println(uf.connected(6, 4));
  }

}

初始化之后的树结构为

每一个父节点都指向自己,经过以上的合并之后的树为

判断两个元素是否连接就是判断两个元素的根节点是否一致。

基于深度的优化

上面的代码虽然功能实现了,但可能出现子链很长的情况,这种情况查询就很低效率。

以上图为例,当我们要合并元素3和元素4的时候,我们应该使用后一种合并方式,将深度小的合并到深度大的。

public class UnionFind2 implements UF {

  private Node[] data;

  public UnionFind2(int size) {
    data = new Node[size];
    for (int i = 0; i < data.length; i++) {
      data[i] = new Node(i, 1);
    }
  }

  @Override
  public boolean connected(int p, int q) {
    rangeCheck(p, q);
    return root(p) == root(q);
  }

  private int root(int p) {
    int cur = p;
    while (true) {
      int parent = data[cur].parent;
      if (parent == cur) {
        return cur;
      }
      cur = parent;
    }
  }

  @Override
  public void union(int p, int q) {
    rangeCheck(p, q);
    int pRoot = root(p);
    int qRoot = root(q);
    if (pRoot == qRoot) {
      return;
    }
    //深度小的指向深度大的
    if (data[pRoot].depth < data[qRoot].depth) {
      data[pRoot].parent = qRoot;
    } else if (data[pRoot].depth > data[qRoot].depth) {
      data[qRoot].parent = pRoot;
    } else {
      data[pRoot].depth += 1;
    }
  }

  public int size() {
    return data.length;
  }

  private void rangeCheck(int p, int q) {
    if (p < 0 || p >= size() || q < 0 || q >= size()) {
      throw new IllegalArgumentException("index is illegal");
    }
  }

  private static class Node {

    int parent;
    int depth;

    Node(int parent, int depth) {
      this.parent = parent;
      this.depth = depth;
    }
  }
}

路径压缩

在查询的时候我们也可以进行优化

private int root(int p) {
    int parent = data[p].parent;
    if (parent == p) {
      return p;
    }
    return data[p].parent = root(parent);
  }

以查询元素4为例,直接将元素4的父节点指向根节点0。

posted @ 2021-01-04 19:47  strongmore  阅读(330)  评论(0编辑  收藏  举报