连通性问题-算法导论

连通性问题指的是:有一组序列,然后再给定一组连通组合,可以判断其中两个元素的连接状态。

用个例子来说就是:如果A和B连接,同时A和C连接,那么就认为B和C是连接的。这里有个约定是连接是双向的。

再生活化一点的例子就比方说:A和B的网络是通的,A和C的网络也是通的,那么,我们可以认为B和C的网络也是通的。(B->A->C)

建模:

我们想象一下,用一个0-N的数组来表示各个节点的连接状态,假定共有10个元素,数组的每一个元素对应与起连接的节点index,我们用简单的遍历方式来修改数组,开始的时候是这样的。

int[] id= {0,1,2,3,4,5,6,7,8,9};

再给出一个连接操作(1,3),那么对应的节点“1”的下标就成为了“3”,a[1] = 3

int[] id= {0,3,2,3,4,5,6,7,8,9};

这时,id[1]和id[3]是一样的。嗯,我们看出来他们连接了。


如果再出一个连接操作(1,4),再改变节点“1”下标改成“4“,额。。。那3怎么办?把3也改成4好了。数组现在是这样:

a[1] = 4
a[3] = 4
id= {0,4,2,4,4,5,6,7,8,9};

这样,一个最简单的连通问题就解决了,现在看看算法的复杂度。

我们使用的是遍历数组的方式,来把1和3都变成4的。

对N个对象的连通性问题,如果执行M次合并操作,那么合并算法至少执行MN(每一次合并操作都要遍历一次数组)次指令。

思考:如何让让程序跑的更快

问题有了,我们思考看看哪里可以提高,对于简单的连通问题我们大概做了这样三件事情(模型不变的情况下): 

  1. 建立数组来存储所有元素 2.做合并操作 3. 查询两个节点是否连接。

1看起来不是我们算法里面的内容。3暂且看起来蛮简单的,只要比较两个节点的内容是否相同,但是这个结果好像蛮依赖合并操作的,简单起见,先不管它。

重点看合并操作,这个操作又可以细分成下面的步骤:比如我们有节点a,b,那么,我们要找到所有与节点a相连的,然后再把它们连上b。

如果我们有一个偷懒的办法,利用数组的某些特性,可以提高效率呢?考虑只有4个元素的{0, 1, 2, 3},节点1和节点3相连,我们有看看有哪些结论?

a[1] = 3
a[3] = 3
a[a[1]] = 3

哈,我们看到一个巧合,a[2]这个节点被我们跳过去了。(当然不是巧合,不然你以为节点为什么要从0开始编号),那么,我们是不是感觉到这样一个树形

于是,我们只要写这一个类似的循环,就能跳过无关(不相连)的点,以下是伪代码

void merge(int p, int q)
{
  validate(p);
  validate(q):
  if (p == q) return;
  int i = a[p];
  int j = a[q];
  while(i != a[i]) { i = a[i]; }
  while(j != a[j]) { j = a[j]; }
  connect(i, j);
}

你可能会怀疑,这样找到“根”的方式,靠谱不靠谱,玩意有两个节点比如m,n 刚好a[m] = n, a[n] = m,不是这不是死循环了吗?

如果真有这两个节点,确实会出现这样的情况,所以,你自己得保证不出现这样的情况。


采用这样的方法,感觉会好一些,但是如果有这样的一组,[1,2,3,4,5,6],0节点连着1,1节点连着2,2...,就算while,还是每个元素都遍历了一遍啊,那就再想想法子,

我能不能尽量出现少的数字类型,上面是1-6,但是其实他们的节点都相连,我们代码再这么改一下

void merge(int p, int q)
{
  validate(p);
  validate(q):
  if (p == q) return;
  int i = a[p];
  int j = a[q];
  // 找到p那边的根
  while(j != a[j]) { j = a[j];}
  // 把a这边的分支包括都连到p那边
  while(i != a[i]) { i = a[i]; a[i] = j;}
  // p的根于q的根相连
  connect(i, j);
}

每次合并的时候都把多余的数字去掉(改成q的根节点)



转载于:https://my.oschina.net/yangcol/blog/197624

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值