1.设计算法的步骤
- 给问题建模
- 设计算法解决
- 满足速度和内存要求?
- 不满足,找出原因
- 寻找更好的方法
- 迭代,直到满意
2.问题描述
有一系列的物体,每两个物体之间相连,问题是:给出任意两个物体,是否有一条路连接其中两个物体?
3.问题建模
性质:
- 自身性:p与p自身相连
- 对称性:如果p与q相连,则q与p相连
- 传递性:如果p与q相连,q与r相连,则p与r相连
连通集:连通集中的任意两个对象都是相连接的。
确定方法:连接操作、是否连接判断
4.快速查找
数组作为该问题的数据结构,数组中每一个元素对应一个物体。数组的下标唯一确定物体(比如说第五个数下标为5,代表着第五号物体),相同数值的物体为相连物体(比如第2个数和第5个数值都为1,则这两个数相连)。
算法 | 初始化 | 连接 | 查询 |
---|---|---|---|
快速查找 | N | N | 1 |
评价:如果使用快速查找算法,初始化加上连接需要N^2时间,我们不能接受平方时间的算法。
5.快速合并
以数组方式储存一个数,角标还是物体的“身份证”,值对应着父节点。查找只需判断p和q是否有同一个根节点,合并只需将p的根节点变为q的根节点的父节点。
由于可能生成很瘦很高的树,在连接和查询时效率比较低,为N。
算法 | 初始化 | 连接 | 查询 |
---|---|---|---|
快速合并 | N | N | N |
6.带权重的快速合并
为了避免生成很高的树,我们要避免把高树连接在矮树的根节点上,而应该把矮树连接在高树的根节点上。操作上,实时记录树的节点总数,根据节点总数大小来判断树的高矮。如图可以看出,本例中,带权重的快速合并树高控制在4以内。
算法 | 初始化 | 连接 | 查询 |
---|---|---|---|
带权重的快速合并 | N | lg N | lg N |
7.代码(Java)
public class UF{
public static void main(String[] args){
StdOut.println("请输入节点个数");
int N = StdIn.readInt();
//***********在此选择算法***********//
// QuickFind uf = new QuickFind(N);
// QuickUnion uf = new QuickUnion(N);
WeightedQuickUnion uf = new WeightedQuickUnion(N);
//********************************//
StdOut.println("请输入节点");
while(!StdIn.isEmpty()){
int p = StdIn.readInt();
int q = StdIn.readInt();
if(q>N-1 || p>N-1) {
StdOut.println("超出范围,请重新输入!");
continue;
}
if(!uf.connected(p, q)){
StdOut.println("未连接");
uf.union(p, q);
}else{StdOut.println("已连接");}
}
}
}
class QuickFind {
private int[] id;
public QuickFind(int N){
id = new int[N];
for(int i=0;i<N;i++){
id[i]=i;
}
}
void union(int p, int q){
int pid = id[p];
int qid = id[q];
for(int i = 0;i<id.length;i++){
if(id[i]==pid) id[i] = qid;
}
};
boolean connected(int p, int q){
return id[p] == id[q];
};
int find(int p){
return id[p];
};
int count(){
return id.length;
};
}
class QuickUnion {//初始化
private int[] id;
public QuickUnion(int N){
id = new int[N];
for(int i=0;i<N;i++){
id[i]=i;
}
}
private int root(int i){
while(i!=id[i]) i = id[i];
return i;
}
void union(int p, int q){
int j = root(p);
int k = root(q);
id[j] = k; //j是k的子树
};
boolean connected(int p, int q){
return root(p)==root(q);
};
int count(){
return id.length;
};
}
class WeightedQuickUnion {//初始化
private int[] id;
private int[] sz;//储存树的大小
public WeightedQuickUnion(int N){
id = new int[N];
for(int i=0;i<N;i++){
id[i]=i;
}
sz = new int[N];
}
private int root(int i){
while(i!=id[i]) i = id[id[i]]; //带路径压缩,两层压缩最小树高为2,三层压缩最小数高为3
return i;
}
void union(int p, int q){
int j = root(p);
int k = root(q);
if(sz[p]>sz[q]){id[p]=k;sz[p]+=sz[q];} //q树连接到p数上
else{id[q]=j;sz[q]+=sz[p];}
};
boolean connected(int p, int q){
return root(p)==root(q);
};
int count(){
return id.length;
};
}