/**
*@ author StormMaybin
*@ Date 2016-09-13
*/
生命不息,奋斗不止!
Union-Find算法
API
Public class UF
UF(int N) //以整数标识(0-N-1)初始化
void union (int p. int q) //连通p和q
int find (int p) //p所在的分量标识
boolean connected (int p, int q) //判断p和q是否在同一个分量中
int count () //连通分量的个数
UF算法的初衷是表示连通类的问题,选择的数据结构正好是数组这种最简单常用的数据结构。
Quick-Find算法实现
package com.stormma.qf;
import java.io.BufferedInputStream;
import java.util.Scanner;
public class QF
{
private int [] id; //分量id
private int count; //分量数量
public QF (int N)
{
//初始化分量id数组
count = N;
id = new int [N];
for (int i = 0; i < N; i++)
id[i] = i;
}
public int count ()
{
return count;
}
public boolean connected (int p, int q)
{
return find(p) == find(q);
}
public int find (int p)
{
return id [p];
}
public void union (int p, int q)
{
//将p和q归并到相同的分量中
int pID = find(p);
int qID = find(q);
//如果p和q已经在相同的分量中则不需要再次连通
if (pID == qID)
{
return ;
}
//将p的分量命名为q的名称
for (int i = 0; i < id.length; i++)
{
if (id[i] == pID)
id[i] = qID;
}
count --;
}
public static void main (String [] args)
{
Scanner scan = new Scanner(new BufferedInputStream(System.in));
int N = scan.nextInt();
UF qf = new QF(N);
while (scan.hasNext())
{
int p = scan.nextInt();
int q = scan.nextInt();
if (qf.connected(p, q))
continue;
else
qf.union(p, q);
System.out.println(p+"---"+q);
}
scan.close();
}
}
我们从上面代码中可以看出,find的时间复杂度是O(1),但是Union时间复杂度是O(n)。如果我们期望最后得到一个连通分量,那么我们需要调用N-1次union(),那么算法复杂度是(N+3)(N-1)~N^2。
Quick Union
旨在提高union()方法的速度。我们可以稍微修改一下find()方法和union()方法,来得到稍微快一点的union。
public int find (int p)
{
while (p != id [p])
p = id[p];
return p;
}
public void union (int p, int q)
{
int pRoot = find (p);
int qRoot = find (q);
if (pRoot == qRoot)
return;
id[pRoot] = qRoot;
count --;
}
现在我们来看一下QF和QU到底什么区别,QF中,id[p]标识p的祖先,对于QF操作union的时候,假设这时候把p分量合并到q分量中去,那么p的这个分量中的所有触点的祖先都是q这个分量的祖先,所以我们在union()方法中遍历开始修改祖先是p的触点的祖先为q分量的祖先。
但是对于QU却不是这样的,QU中定义的id[p]标识的是p的父亲,现在我们要找到p的祖先只能一直网上找(找p的父亲,如果p的父亲不是祖先,那我们接着找p的父亲的父亲······)。然后操作union的时候我们找到了p的祖先,合并p和q分量的时候,只需要把p的祖先的父亲设置为q的祖先,这样就可以了,这样看来union速度确实是快了一点,但是find速度变慢了。假设这时候我们有一组数是0 1 2 3 4 5 6 ······N-1,我们先把0连接到1,然后把1连接到2,2连接到3,N-2连接到N-1,这时候我们需要访问find()方法的次数就变成了2(1+2+3+······N)~N^2。继续改进算法
Weighted Quick - union (加权QU)
package com.stormma.wqu;
public class WQU
{
private int[] id;
private int[] sz;// 分量大小
private int count;
public WQU(int N)
{
count = N;
id = new int[N];
for (int i = 0; i < N; i++)
id[i] = i;
sz = new int[N];
for (int i = 0; i < N; i++)
sz[i] = 1;
}
public int count()
{
return count;
}
public boolean connected(int p, int q)
{
return find(p) == find(q);
}
public int find(int p)
{
while (p != id[p])
p = id[p];
return p;
}
public void union(int p, int q)
{
int i = find(p);
int j = find(q);
if (i == j)
return;
// 小数根节点连到大数根节点
if (sz[i] < sz[j])
{
id[i] = j;
sz[j] += sz[i];
}
else
{
id[j] = i;
sz[i] += sz[j];
}
count --;
}
}
前面的QU之所以慢,是因为最坏情况下会出现把大数连接到小数的情况,那么我们在WQU这算法中维护一个数组,来维护i这个结点的深度(即大小),这样我们在操作union的时候,我们始终把小的树连接到大树上去!代码中sz数组就是这个含义。其他地方和QU算法差不多。