问题描述:给定了一组原始数据对,每个数据对<p, q>表示点p和点q相连。输入新的数据对<x,y>, 判断x,y是否已经在原始数据对中相连,如果不是,则输出x,y。
这个问题有很多的变形,比如A认识B,A认识C,那么A是C的间接好友,如果有很多的这种人物关系当做原始数据集,再让判断新输入的两个人是否是间接好友,则就可以用这里的动态连接性。
最慢的实现方法: quick_find
import java.util.Scanner;
public class UnionFound
{
private int[] id; //用以存放每个节点的索引号
private int count; // 连通分量的个数
public UnionFound(int N)
{
count = N;
id = new int[N];
for(int ii=0; ii<N; ++ii)
id[ii] = ii;
}
public int count()
{
return count;
}
// quick_find 算法
public int find(int p)
{
return id[p];
}
public void union(int p, int q)
{
int pId = id[p];
int qId = id[q];
if(pId == qId)
return;
for(int ii=0; ii<id.length; ++ii) //遍历所有的节点,将所有和p相同索引的节点都赋值为q.
{
if(pId == id[ii])
id[ii] = qId;
}
count--;
}
public boolean connected(int p, int q)
{
return find(p)==find(q);
}
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
UnionFound unionFound = new UnionFound(N);
int[][] array = new int[][] {{4, 3}, {3, 8}, {6, 5}, {9, 4}, {2,1}, {8,9}, {5,0}, {7,2}, {6,1}, {1,0}, {6,7}};
for(int ii=0; ii<array.length; ++ii)
{
int p = array[ii][0];
int q = array[ii][1];
if(unionFound.connected(p, q))
continue;
unionFound.union(p, q);
StdOut.println(p + " " + q);
}
while(scanner.hasNext())
{
int p = scanner.nextInt();
int q = scanner.nextInt();
if(unionFound.connected(p, q))
continue;
unionFound.union(p, q);
StdOut.println(p + " " + q);
}
}
}
quick_find的改进:quick_union
该方法采用了树的数据结构,因此对于普通数据的计算量会减少很多,但是由于没有涉及树的合并方式,因此算法比较粗糙,在极端情况下(比如,1连2, 2-3, 3-4, …, N-1连N),算法的复杂度与quick_find相同。
import java.util.Scanner;
public class UnionFound
{
private int[] id;
private int count;
public UnionFound(int N)
{
count = N;
id = new int[N];
for(int ii=0; ii<N; ++ii)
id[ii] = ii;
}
public int count()
{
return count;
}
// quick_union算法
public int find(int p) //找到任意节点所对应的根节点
{
while(id[p] != 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--;
}
public boolean connected(int p, int q)
{
return find(p)==find(q);
}
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
UnionFound unionFound = new UnionFound(N);
int[][] array = new int[][] {{4, 3}, {3, 8}, {6, 5}, {9, 4}, {2,1}, {8,9}, {5,0}, {7,2}, {6,1}, {1,0}, {6,7}};
for(int ii=0; ii<array.length; ++ii)
{
int p = array[ii][0];
int q = array[ii][1];
if(unionFound.connected(p, q))
continue;
unionFound.union(p, q);
StdOut.println(p + " " + q);
}
while(scanner.hasNext())
{
int p = scanner.nextInt();
int q = scanner.nextInt();
if(unionFound.connected(p, q))
continue;
unionFound.union(p, q);
StdOut.println(p + " " + q);
}
}
}
稳定的对数复杂度:加权quick_union
相对于常规的quick_union算法,加权quick_union算法先会判断待连接的两个树的大小,并总是将小的树连接到大的树上,因此,树的高度可以稳定在lgN。
public class WeightedQuickUnionFound
{
private int[] id; // 父链接数组
private int[] sz; // 各个根节点所对应的分量的大小
private int count; // 连通分量的数量
public WeightedQuickUnionFound(int N)
{
count = N;
id = new int[N];
sz = new int[N];
for(int ii=0; ii<N; ++ii)
{
id[ii] = ii;
sz[ii] = 1;
}
}
public int find(int p)
{
while(id[p] != 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;
if(sz[pRoot] <= sz[qRoot])
{
id[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else
{
id[qRoot] = pRoot;
sz[pRoot] += qRoot;
}
count--;
}
public boolean connected(int p, int q)
{
return find(p)==find(q);
}
public int count()
{
return count;
}
public static void main(String[] args)
{
int[][] array = new int[][] {{4, 3}, {3, 8}, {6, 5}, {9, 4}, {2,1}, {8,9}, {5,0}, {7,2}, {6,1}, {1,0}, {6,7}};
WeightedQuickUnionFound unionFound = new WeightedQuickUnionFound(array.length);
for(int ii=0; ii<array.length; ++ii)
{
int p = array[ii][0];
int q = array[ii][1];
if(unionFound.connected(p, q))
continue;
unionFound.union(p, q);
StdOut.println(p + " " + q);
}
}
}