Disjoint Sets
OK,在这节中我们要做什么呢?我们希望可以有一种方法可以连接两个元素,并且对于给定的两个元素判断它们是否连接。针对这一问题我们将在这一节中给出几种解决的方式,每种方式都有着不同的时间复杂度。
思路设计:
我们肯定是需要两个方法,第一个方法用于连接给定的两个元素;第二个方法用于检测两个给定的元素是否连接。
interface DisjointSet
public interface DisjointSets {
/** connects two items p and q */
void connect(int p, int q);
/** checks to see if two items are connected */
boolean isConnected(int p, int q);
}
就如上面设计思路中所说的,我们创建了两个方法。connect()
用于连接、isConnected()
用于检测是否连接。
连接方式
我们如何表示连接呢,方法可能有很多种。CS61B中给出的方法是用集合表示连接,举个例子:
i.e. {0}, {1}, {2}, {3}, {4}, {5}
connect(0, 1) -> {0, 1}, {2}, {3}, {4}, {5}
connect(1, 2) -> {0, 1, 2}, {3}, {4}, {5}
connect(4, 5) -> {0, 1, 2}, {3}, {4, 5}
connect(0, 2) -> {0, 1, 2}, {3}, {4, 5} (这里注意到,如果重复连接已经存储在一个集合中的元素,不发生改变)
isConnected(1, 2) -> true
isConnected(0, 4) -> false
connect(1, 4) -> {0, 1, 2, 4, 5}, {3}
isConnected(0, 4) -> true (这里注意,isConnected的结果是随着程序的进行而动态变化的)
QuickFindDS
那么我们来思考如何用语言来表示连接两个元素,首先给出一种最直白的方式,Quick Find,思路如下:
- 我们可以先创建一个列表,变将其初始化,让每一个列表中的元素等于其index:
private int[] id;
public QuickFindDS(int N) {
id = new int[N];
for(int i=0; i<N; i+=1) {
id[i] = i;
}
}
这样我们得到了一个初始化后的链表
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 |
- 我们找到处于连接状态的元素在链表中的位置(index),并将该位置的值设定为相同值:
举个例子,在这种给定的状态下{0, 1, 2}, {3}, {4, 5}
,我们的链表如下:
2 | 2 | 2 | 3 | 5 | 5 |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 |
我们通过这种表示方式来体现链表的连接,当我们再执行指令connect(0, 4)时,链表会变成这样:
5 | 5 | 5 | 3 | 5 | 5 |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 |
所以我们的连接方法如下:
public void connect(int p, int q) {
int pIndex = id[p];
int qIndex = id[q];
for(int i=0; i< id.length; i++) {
if(id[i] == pIndex) {
id[i] = qIndex; //sets all connected items the same value
}
}
}
- 最后的一步——检查是否连接就变得尤为简单了,只需要检测两个元素对应(index)