#正文
最近在学习<<算法>>这本书,里面关于动态连接性问题让人印象深刻,何为动态连接性:设计一个数据结构来存储已知的连接信息,并能用其来判断一堆新对象是否是相连的。
例如输入p,q则可以理解为p,q是相连的,同时它具有:
1. 自反性,p和q是相连的
2. 对称性,p,q相连则q,p也是相连的
3. 传递性,p,q相连&&q,r相连则p,r相连
废话少说,要解决的问题是这样的:已知有一定数量的点,每一个点都是独立的,然后每次输入两个点之间的联系来说明两个点是相连的,如果两个点之间之前并没有相连则把这两个点连起来,如果两个点已经相连则忽略。
例如有6个已经标识的点:
设计基本的API来进行操作:
| 方法 | 描述 |
|---|---|
| UF | 构造函数 |
| void union(int p,int q) | 连接p,q两点 |
| int find(int p) | 返回p所在分量(或者说是集合)的标识 |
| boolean connected(int p,int q) | p,q两点在同一分量返回true反之false |
| int count() | 返回所有分量的数量 |
代码模板如下:
public class UF {
private int [] id;
private int count;
public UF(int N){
count = N;
id = IntStream.iterate(0, (x)->x+1).limit(N).toArray();
}
void union(int p,int q) {
}
int find(int p) {
return 0;
}
boolean connected(int p,int q) {
return find(p)==find(q);
}
int count() {
return count;
}
}
接着书中介绍了三种解决的算法,quick-find,quick-union以及加权quick-union算法。
quick-find
quick-find算法主要流程:对于输入两点p,q,如果p,q两点已经在同一分量内,则不采取任何操作。
如果两点不在同一分量则将一方分量内所有点名称改为另一方的名称。
public class UF {
private int [] id;
private int count;
public UF(int N){
count = N;
id = IntStream.iterate(0, (x)->x+1).limit(N).toArray();
}
void union(int p,int q) {
int pId = find(p);
int qId = find(q);
if(pId == qId) return; //如果p,q两点在同一分量内
IntStream.of(id).filter((i) -> i==qId).forEach((i) -> i=qId); //两点不在同一分量,将一点所在分量名称改为另一点的名称
count--;
}
int find(int p) {
return id[p];
}
boolean connected(int p,int q) {
return find(p)==find(q);
}
int count() {
return count;
}
}
quick-find算法缺点很明显,就是union的时候对于输入的每一组p,q,合并都需要foreach一回,效率堪忧啊。
quick-union
quick-union算法与quick-find算法最大不同是quick-union算法用树来表示点之间的连接信息。
id数组用来存储当前点的根节点(其实说父节点更好理解一点),find函数则是找出当前点的根节点,那么union函数就简单了:将不在同一分量内的点的更节点设置为另一个节点的更节点就完事了。
void union(int p,int q) {
int pId = find(p);
int qId = find(q);
if(pId == qId) return; //如果p,q两点在同一分量内
id[pId] = qId; //将p的根节点设为q节点
count--;
}
int find(int p) {
while(p!=id[p])
p = id[p];
return p;
}
代码更简洁也更有效率了。但是quick-union可能会有大树连接到小树的情况,树会越来越高,find函数每次执行的时间就会越来越长。
加权quick-union
加权quick-union相对于quick-union增加了存储权值的数组,每次合并时比较合并两方权值大小,小的一方将大的一方的根节点设为自己的根节点,大的一方权值+=小的权值。
public class UF {
private int [] id;
private int [] sz;
private int count;
public UF(int N){
count = N;
id = IntStream.iterate(0, (x)->x+1).limit(N).toArray();
sz = IntStream.iterate(1, x->x).limit(N).toArray();
}
void union(int p,int q) {
int pId = find(p);
int qId = find(q);
if(pId == qId) return; //如果p,q两点在同一分量内
if(sz[p]<sz[q]) { //权值比较,避免大树加到小树上
id[p] = qId;
sz[qId] += sz[pId];
}else {
id[q] = pId;
sz[pId] += sz[qId];
}
count--;
}
int find(int p) {
while(p!=id[p])
p = id[p];
return p;
}
boolean connected(int p,int q) {
return find(p)==find(q);
}
int count() {
return count;
}
}
总结
动态联通性问题,在解决一些实际问题时非常有效,比如计算机网络的连通性问题以及社交网络的朋友关系问题。感觉学习到了很多东西。