算法 - 并查集

418 阅读4分钟

本文为小码哥推出的恋上数据结构与算法笔记,如有兴趣,请报班学习

基础概念

  • 并查集也叫做不相交集合(Disjoint set)
  • 并查集有两个核心操作
    • 查找(find):查找元素所在的集合(这里的集合并不是特指Set这种数据结构,是指广义的数据集合)
    • 合并(Union):将两个元素所在的集合合并为一个集合
  • 有2种常见的实现思路
    • Quick Find
      • 查找(Find)的时间复杂度:O(1)
      • 合并(Union)的时间复杂度:O(n)
    • Quick Union
      • 查找(Find)的时间复杂度:O(logn),可以优化至O(a(n)), a(n) < 5
      • 合并(Union)的时间复杂度:O(logn),可以优化至O(a(n)), a(n) < 5

如何存储数据?

接口定义

/**
 * 查找v所属的集合(根节点)
 * @param v
 * @return
 */
public abstract int find(int v);

/**
 * 合并v1、v2所在的集合
 */
public abstract void union(int v1, int v2);

/**
 * 检查v1、v2是否属于同一个集合
 */
public boolean isSame(int v1, int v2);

初始化

protected int[] parents;

public UnionFind(int capacity) {
  if (capacity < 0) {
      throw new IllegalArgumentException("capacity must be >= 1");
  }
  parents = new int[capacity];
  for (int i = 0; i < capacity; i++) {
     parents[i] = i;
  }
}

Quick Find

Union

Quick Findunion(V1,V2): 让V1所在集合的所有元素都指向V2的根节点

public void union(int v1, int v2) {
   int p1 = parents[v1];
   int p2 = parents[v2];
   if (p1 == p2) return;
	
   for (int i = 0; i < parents.length; i++) {
	if (parents[i] == p1) {
		parents[i] = p2;
	}
   }
}

Quick-FindUnion操作的时间复杂度:O(n)

Find

可以看出,Quick Find执行Union之后的树形结构非常矮,可以直接通过数组来查找根节点

public int find(int v) {
    rangeCheck(v);
    return parents[v];
}

Quick-FindFind操作的时间复杂度:O(1)

Quick Union

Quick UnionUnion(v1,v2):让v1的根节点指向v2的根节点.

Union

/**
 * 将v1的根节点嫁接到v2的根节点上
 */
@Override
public void union(int v1, int v2) {
  int p1 = find(v1); // 查找根节点
  int p2 = find(v2);
  if (p1 == p2) return;
  parents[p1] = p2;
}

时间复杂度:O(logn)

Find

可以看出,Quick Union下的Union之后,数据呈现出树状排布,因此查找操作是顺着树状查找

  • find(0) == 2
  • find(1) == 2
  • find(3) == 2
  • find(2) == 2
  • 时间复杂度:O(logn)
/**
 * 通过parent链条不断地向上找,直到找到根节点
 */
@Override
public int find(int v) {
  rangeCheck(v);
  while (v !=  parents[v]) {
    v = parents[v];
  }
  return v;
}

Quick Union - 优化

  • Union的过程中,可能会出现树不平衡的情况,甚至退化成链表
  • 有2种常见的优化方案
    • 基于size的优化:元素少的树 嫁接到 元素多的树
    • 基于rank的优化:矮的树 嫁接到 高的树

Quick Union - 基于Size的优化

元素少的树 嫁接到 元素多的树

// 创建一个数组,用来统计树的节点数量
private int[] sizes;

public UnionFind_QU_S(int capacity) {
	super(capacity);
	// TODO Auto-generated constructor stub
	sizes = new int[capacity];
	for (int i = 0; i < parents.length; i++) {
		sizes[i] = 1;
	}
}

基于Size的优化,不需要改变find的逻辑,只需要改变Union的逻辑

/**
 * 将v1的根节点嫁接到v2的根节点上
 */
@Override
public void union(int v1, int v2) {
	int p1 = find(v1);
	int p2 = find(v2);
	if (p1 == p2) return;
	if (sizes[p1] < sizes[p2]) {
		parents[p1] = p2;
		sizes[p2] += sizes[p1];
	} else {
		parents[p2] = p1;
		sizes[p1] += sizes[p2];
	}
}

基于size的优化,也可能会存在树不平衡的问题

由此引出基于rank的优化

Quick Union 基于rank的优化

矮的树 嫁接到 高的树

// 创建一个数组,用来统计树的高度
private int[] ranks;

public UnionFind_QU_R(int capacity) {
	super(capacity);
	// TODO Auto-generated constructor stub
	ranks = new int[capacity];
	for (int i = 0; i < parents.length; i++) {
		ranks[i] = 1;
	}
}

基于Rank的优化,不需要改变find的逻辑,只需要改变Union的逻辑

/**
 * 将v1的根节点嫁接到v2的根节点上
 */
@Override
public void union(int v1, int v2) {
	int p1 = find(v1);
	int p2 = find(v2);
	if (p1 == p2) return;
	if (ranks[p1] < ranks[p2]) {
		parents[p1] = p2;
	} else if (ranks[p1] > ranks[p2]) {
		parents[p2] = p1;
	} else {
		parents[p1] = p2;
		ranks[p2]++;    // 只有树的高度相同时,才需要更新树的高度
	} 
}

路径压缩 (Path Compression) - 基于rank优化

@Override
public int find(int v) {
  rangeCheck(v);
  if (parents[v] != v) {
    parents[v] = find(parents[v]);
  }
  return parents[v];
}
  • 路径压缩使路径上的所有节点都指向根节点,所以实现成本稍高。
  • 还有2种更优的做法,不但能降低树高,实现成本也比路径压缩低
    • 路径分裂(Path Spliting)
    • 路径减半(Path Halving)

路径分裂(Path Spliting) - 基于rank优化

使路径上的每个节点都指向其祖父节点(parentparent)

public int find(int v) {
	// TODO Auto-generated method stub
	rangeCheck(v);
	while (v != parents[v]) {
		int p = parents[v];
		parents[v] = parents[parents[v]];
		v = p;
	}
	return v;
}

路径减半(Path Halving) - 基于rank优化

使路径上每隔一个节点就指向其祖父节点(parentparent)

@Override
public int find(int v) {
	// TODO Auto-generated method stub
	rangeCheck(v);
	while (v != parents[v]) {
		parents[v] = parents[parents[v]];
		v = parents[v];
	}
	return v;
}