问题描述:
问题是输入一列整数对,,其中每个整数都表示一个某种类型的对象,一对整数p,q被理解为相连的,我们假设相连是一种等价关系,即具备自反,堆成,传递三种特性。
当从程序中读取整数对时,如果已知整数对都不能说明p,q是相连的,那么则将这一对整数对写入输出中,如果已知数据对可以说明p,q是相连的,那么就会略这一对p,q,读取下一对输入。
比如 1 2
3 4
输入1,2 2,4 3,4三对数据之后,输入1,3这个整数对输入将会被忽略,因为1,3已经相连。
算法改进:
这里我们实现一种quick-find算法,它避免了循环,通过一种更优化的信息维护方式来完全的表明了这个问题的关键信息:哪些点是连通的。
改进之前的算法:每个角标(点)对应的数组值都是一个相同的数,那么,每一组具有相同的数组值的角标(点)就是一组连通的分量,这个时候每次把一个点加进去的时候,为了找到所有的与它向连通的点,那么一定需要循环遍历数组。
那么,我们要改进算法,为了不遍历数组,我们考虑建立链接这种联系,即每个点的数组值存储的是下一个与它相连的点。这个思考的实质来自于链表结构的查询快于顺序结构的查询。
那么关键问题来了,我们如何来表明一个点是属于哪一个连通分量呢,我们考虑链表的最后一个结点(因为可以通过链表中的任意一个结点顺序查找就能找到它),以每个连通分支(链表)的最后一个结点来唯一表明连通分量的身份。
判断:我们如何来判断两个点是否是属于同一个链表呢,由上面的分析可以看出,我们比较两个点一直往后走,走到最后一个结点,比较这个结点是否相同。必要条件是,每个链就并不是输入的两个点相连了,这样会可能导致最后一个结点不唯一。所以只能将两个点能找到的最后一个结点相连(这是唯一使得两条链相连之后还是一条链的方法)。这样就不能维护到底输入的时候是哪两个点两两相连了,只能维护到底哪些点属于一条链。
注:最后一个结点的值就是本身的角标,这也成为判断依据。(因为后面没有连点),当然如果只有一个点就是本身。
想象一下:多次连接之后,最后共同的那个最后的结点就是最中心的交汇点,就像花一样向外辐射链表。
import java.util.Scanner;
public class UnionFind {
private int[] arr;
public UnionFind(int n)
{
arr = new int[n];
for(int i=0; i<n; i++)
{
arr[i] = i;
}
}
public boolean connected(int p, int q) //判断
{
return lastnode(p)==lastnode(q);
}
private int lastnode(int p)
{
while(p != arr[p]) //如果不是最后一个结点就一直找
p = arr[p];
return p; //返回最后一个结点(只有一个结点就是本身)
}
public void union(int p, int q) //连接
{
int plastnode = lastnode(p);
int qlastnode = lastnode(q);
arr[plastnode] = qlastnode;
}
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
int n = in.nextInt();
UnionFind uf = new UnionFind(n);
while(true)
{
int p = in.nextInt();
if(p == -1) break;
int q = in.nextInt();
if(q == -1) break;
if(uf.connected(p,q))
continue;
uf.union(p, q);
System.out.println(p + " " + q);
}
}
}
输入测试:
10
4 3
3 8
6 5
9 4
2 1
8 9
5 0
7 2
6 1
1 0
6 7
-1
结果输出:
4 3
3 8
6 5
9 4
2 1
5 0
7 2
6 1