1. 并查集(Union Find)
也叫做不相交集合(Disjoint Set)
- 查找(Find):查找元素所在的集合;
- 合并(Union):将两个元素所在的集合合并为一个集合;
1.1 存储方式
假设并查集处理的数据都是整性,可以使用整性数组来存储数据。
- 存储结构
- 逻辑结构
可以看出:(0、1、3)是一个集合;(4、5、6、7)是一个集合;2自己一个集合。
1.2 抽象类设计
public abstract class AbstractUnionFind {
protected int[] parents;
public AbstractUnionFind(int capacity) {
if (capacity < 0){
throw new IllegalArgumentException("Capacity must >= 1");
}
parents = new int[capacity];
for (int i = 0; i < parents.length; i++) {
parents[i] = i;
}
}
protected void rangeCheck(int index){
if (index >= parents.length || index < 0){
throw new IndexOutOfBoundsException("Index must less than array's length ");
}
}
public abstract int find(int v);
public abstract void union(int v1, int v2);
public boolean isSame(int v1, int v2){
return find(v1) == find(v2);
}
}
1.3 初始化
在执行初始化的时候,将值的父节点指向自己表示自己为一个单独的集合。
private int[] parents;
public QuickFind(int capacity) {
if (capacity < 0){
throw new IllegalArgumentException("Capacity must >= 1");
}
parents = new int[capacity];
for (int i = 0; i < parents.length; i++) {
parents[i] = i;
}
}
1.4 Quick Find方式实现并查集
1.4.1 union
union(v1, v2) 将v1 集合的所有元素都指向v2 集合的根节点。
- 初始化之后:
union(1, 0) :将1指向0的根节点。 union(1,2) :将1所指向节点的所有原始的根节点都改为2的根节点- 代码实现:
@Override
public void union(int v1, int v2) {
int p1 = find(v1);
int p2 = find(v2);
if (p1 == p2) return;
for (int i = 0; i < parents.length; i++) {
if (parents[i] == p1){
parents[i] = p2;
}
}
}
1.4.2 find
直接返回数组中存储的根节点即可。
private void rangeCheck(int index){
if (index >= parents.length || index < 0){
throw new IndexOutOfBoundsException("Index must less than array's length ");
}
}
@Override
public int find(int v) {
rangeCheck(v);
return parents[v];
}
1.4.3 总结
union() 的时间复杂度为O(n) ;find() 的时间复杂度为O(1) 。
1.5 QuickUnion
1.5.1 union
union(v1, v2) 将v1 集合的根节点都指向v2 集合的根节点。
- 初始化之后:
union(1, 0) :将1的根节点指向0的根节点: union(1,2) :将1的根节点指向2的根节点: union(4,1) :将4的根节点指向1的根节点:
@Override
public void union(int v1, int v2) {
int p1 = find(v1);
int p2 = find(v2);
if (p1 == p2) return;
parents[p1] = p2;
}
1.5.2 find
@Override
public int find(int v) {
rangeCheck(v);
while (v != parents[v]){
v = parents[v];
}
return v;
}
1.5.3 优化
在进行union() 时,可以会出现树不平很的情况,甚至有可能会退化成链表。
1.5.3.1 基于size的优化
元素少的树嫁接到元素多的树上。
public class QuickUnionSize extends AbstractUnionFind{
private int[] sizes;
public QuickUnionSize(int capacity) {
super(capacity);
sizes = new int[capacity];
for (int i = 0; i < sizes.length; i++) {
sizes[i] = 1;
}
}
@Override
public int find(int v) {
rangeCheck(v);
while (v != parents[v]){
v = parents[v];
}
return v;
}
@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];
}
}
}
1.5.3.2 基于rank的优化
矮的树嫁接到高的树上。
public class QuickUnionRank extends AbstractUnionFind{
private int[] ranks;
public QuickUnionRank(int capacity) {
super(capacity);
ranks = new int[capacity];
for (int i = 0; i < ranks.length; i++) {
ranks[i] = 1;
}
}
@Override
public int find(int v) {
rangeCheck(v);
while (v != parents[v]){
v = parents[v];
}
return v;
}
@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] += 1;
}
}
}
1.5.3.3 基于rank的优化 —— 路径压缩
在find() 时,将路径上的所有节点都指向根节点,从而降低树的高度。
public class QuickUnionPC extends AbstractUnionFind{
private int[] ranks;
public QuickUnionPC(int capacity) {
super(capacity);
ranks = new int[capacity];
for (int i = 0; i < ranks.length; i++) {
ranks[i] = 1;
}
}
@Override
public int find(int v) {
rangeCheck(v);
if (v != parents[v]){
parents[v] = find(parents[v]);
}
return parents[v];
}
@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] += 1;
}
}
}
1.5.3.4 基于rank的优化 —— 路径分裂
find() 时,使路径上的每个节点都指向其祖父节点。
public class QuickUnionPS extends AbstractUnionFind{
private int[] ranks;
public QuickUnionPS(int capacity) {
super(capacity);
ranks = new int[capacity];
for (int i = 0; i < ranks.length; i++) {
ranks[i] = 1;
}
}
@Override
public int find(int v) {
rangeCheck(v);
while (v != parents[v]){
int p = parents[v];
parents[v] = parents[parents[v]];
v = p;
}
return parents[v];
}
@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] += 1;
}
}
}
1.5.3.5 基于rank的优化 —— 路径减半
find() 时,使路径上每隔一个节点就指向其祖父节点。
public class QuickUnionPV extends AbstractUnionFind{
private int[] ranks;
public QuickUnionPV(int capacity) {
super(capacity);
ranks = new int[capacity];
for (int i = 0; i < ranks.length; i++) {
ranks[i] = 1;
}
}
@Override
public int find(int v) {
rangeCheck(v);
while (v != parents[v]){
parents[v] = parents[parents[v]];
v = parents[v];
}
return v;
}
@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] += 1;
}
}
}
1.5.4 总结
union() 的时间复杂度为O(n) ;find() 的时间复杂度为O(n) 。
- 使用
rang 或者size 在添加以下三个中的一个路径分裂、路径减半、路径压缩之后:可以使两个操作的均摊时间复杂度优化为:O(α(n)) 其中α(n) < 5 。 - 建议使用
QuickUnion 基于Rank 优化再添加路径分裂或者路径减半。
1.6 自定义类型
- 使用方法将自定义类型转为整型(比如生成哈希值,但要保证哈希值不相同)。
- 链表 + map
|