您的位置:首页 > 其它

连通问题算法

2009-07-21 08:50 204 查看
写一段程序以实现“给出两点,判断其是否连通”。

这个题目可以应用于很多实际问题,如:两个城市间是否有铁路相连,两个电子元件是否有电路相连,两个终端是否有网络相连……此算法仅仅判断是否连通,如果还要求给出连通的具体路径,难度将陡然增加,并且会把问题引入另一个领域——图。
我的第一感觉是把所有节点用一个二维数组存储。在草纸上稍加勾画后便会发现几个问题:
1) 对于N个节点,需要N * N 个空间存储,当连通较少时大量的空间将会闲置,无论任何时候,浪费都是一种罪过;
2) 实现困难,不信的话就自己试试吧;
3) 这毫无疑问是最糟糕的方法。
其实稍加思考可以将空间减少指数级:用一维数组构造连通器,如果节点p、q连通,则令id[p] == id[q]。
下面是代码片段:

public abstract class Arithmetic {

protected int id[] = new int[Constant.N];

public Arithmetic() {
init();
}

private void init() {
for(int i = 0; i < Constant.N; i++) {
id[i] = i;
}
}

public abstract void createConnect(int p, int q);

public abstract boolean isConnect(int p, int q);
}

public class Arithmetic_1 extends Arithmetic {

public Arithmetic_1() {
super();
}

@Override
public void createConnect(int p, int q) {
int t = id[p];
for(int i = 0; i < Constant.N; i++) {
if(id[i] == t)
id[i] = id[q];
}
}

@Override
public boolean isConnect(int p, int q) {
return id[p] == id[q];
}
}

为其编写单元测试:

public class MockArithmetic_1 extends Arithmetic_1 {

public MockArithmetic_1() {
super();
}

public void createConnectTemplete() {
createConnect(1, 2);
createConnect(2, 3);
createConnect(4, 5);
createConnect(3, 5);
}
}

public class TestArithmetic_1 extends TestCase {

private MockArithmetic_1 ac_1;

public void setUp() {
ac_1 = new MockArithmetic_1();
}

public void testCreateConnect() {
ac_1.createConnectTemplete();

this.assertTrue(ac_1.isConnect(1, 5));
this.assertTrue(ac_1.isConnect(2, 5));
this.assertFalse(ac_1.isConnect(2, 9));
}
}

public void createConnect(int p, int q) {

int i,j;
for(i = p; i != id[i]; i = id[i]);
for(j = q; j != id[j]; j = id[j]);

if(i != j)
id[i] = j;
}

public boolean isConnect(int p, int q) {

int i,j;
for(i = p; i != id[i]; i = id[i]);
for(j = q; j != id[j]; j = id[j]);

return i == j;
}


运行一下,测试条变成了愉快的绿色。
上面的代码可以解决一下小问题。当我试着对10,000个顺序自然数构随机构造10,000次连通时,它花了我一杯咖啡的时间!估计再增加一个0,想要得到结果就得等到“中国老百姓看病最不难”那天真正来临。
分析一下效率就知道,对N个数的M次连通,每次构造连通器都会花费N次循环,效率是N*M。如果看成一棵树的话,每次构造连通都将把整棵树毁掉,然后重新打造;如果把两个棵树合并,则需要同时毁掉两棵树,这可真够呛!
把两棵树合并的最简方法就是直接把树根合并,按照这个思路改进的代码如下:

通过指针追溯的方式回答两点是否连通。
同样对10,000个顺序自然数构随机构造10,000次连通的测试,虽然可以马上产生连通器,但是在判断是否连通时花费了一些时间,对于大数据的实践,这个算法同样不适用。
方法二的结果似乎严重依赖输入,它通常枝繁叶茂,但有可能变得比摩天大楼还高,而且营养不良,正是这点严重影响了isConnect的效率。
如果你的方法可以改进,那么它通常可以进一步改进。接下来要做的就是使摩天大楼变成古老的中式建筑群。很简单,在方法二的基础上为树加权,使用另外一个数组sz[]来维护每棵连通树的节点个数,每次合并都会将较小的树连接到较大的树上。
private int sz[] = new int[Constant.N];

public Arithmetic_3() {
super();

for(int i = 0; i < Constant.N; i++)
sz[i] = 1;
}

@Override
public void createConnect(int p, int q) {

int i,j;
for(i = p; i != id[i]; i = id[i]);
for(j = q; j != id[j]; j = id[j]);

if(i != j) {
//将较小的树连接到较大的树上
if(sz[i] < sz[j]) {
id[i] = j;
sz[j] += sz[i];
}
else {
id[j] = i;
sz[i] += sz[j];
}
}
}

这次我甚至可以轻松的对1,000,000个顺序自然数构随机构造1,000,000次连通并很快得到两点是否连通的答案。
最近为东方有线单据系统调试了很多性能问题,一个显而易见的结论是:一个糟糕的算法可以轻易耗尽计算机资源,让双核CPU顷刻间变成两只蜗牛。性能往往在不经意间流失,只要认真思考就能挽回一个甚至几个数量级的时间,要知道,时间就是¥。
这次我甚至可以轻松的对1,000,000个顺序自然数构随机构造1,000,000次连通并很快得到两点是否连通的答案。
最近为东方有线单据系统调试了很多性能问题,一个显而易见的结论是:一个糟糕的算法可以轻易耗尽计算机资源,让双核CPU顷刻间变成两只蜗牛。性能往往在不经意间流失,只要认真思考就能挽回一个甚至几个数量级的时间,要知道,时间就是¥。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: