问题描述;
输入一列整数对,其中每个整数都表示一个某种类型的对象,一对整数p,q被理解为相连的,我们假设相连是一种等价关系,即具备自反,堆成,传递三种特性。
当从程序中读取整数对时,如果已知整数对都不能说明p,q是相连的,那么则将这一对整数对写入输出中,如果已知数据对可以说明p,q是相连的,那么就会略这一对p,q,读取下一对输入。
比如 1 2
3 4
输入1,2 2,4 3,4三对数据之后,输入1,3这个整数对输入将会被忽略,因为1,3已经相连。
我们先来思考思路的雏形:我们循环输入整数对p,q,然后判断p,q是否已经相连,如果已经相连,则直接开始下一次循环,否则连接p,q。同时打印输出。
显然这里判断和连接两个功能,我们都需要定义实现函数(操作)。
那么问题来了,操作基于的数据结构呢?
问题的关键是我们要存储一个信息:每个点到底属于哪个连通的分支,这是我们需要维护的核心信息。由于我们要维护的信息只有一个,天然的数组就成了我们的好选择,因为如果以数组的角标(索引)为研究对象,那么角标(索引)对应的值恰恰就能存储一个信息,比方说:
Example: a a a b b b c c c 这一行是数组值
0 1 2 3 4 5 6 7 8 这一行是数组角标
以上数组是不是就存储了信息:0,1,2属于a类,3,4,5属于b类,6,7,8属于c类呢?
最核心的问题解决了,我们的整个算法思路已经畅通无阻:我们用数组作为数据结构,并定义判断,连接两个操作。
判断只需要返回两个角标数组值,比较是否相等即可,当然我们需要先全部设置为不同,也即是初始化,为了初始化和后续操作的方便,我们采用int型
连接就是要改变数组值:将数组中所有和p连通的角标的数组值都改为q的角标的数组值。
数组值就可以也应该采用所有连接的角标的任意一个角标的数组值作为所有相连角标的数组值,因为一开始初始化的时候,所有角标的值就是不同的。所以不管用哪一个角标的值作为这个一个连通分量所有角标的值,都不会与其他连通分量冲突。
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 arr[p] == arr[q];
}
public void union(int p, int q) //连接
{
int pid = arr[p];
int qid = arr[q];
for(int i=0; i<arr.length; i++)
{
if(arr[i] == pid)//将所有与一者相连的点的值
arr[i] = qid; //全部改为另一者的值(当然也包括与另一者的所有相连点)
}
}
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
小结:对于这个问题的解决:
1. 理清核心步骤的雏形,我们发现了要实现判断 和连接
2. 分析关键问题,怎么维护每个点属于哪个连通分支,想到数组
3. 构建数组,用构造函数初始化,实现两个方法。