连通性问题

问题提出:

假定给定整数对的一个序列,其中每个整数表示某种类型的一个对象,我们想要说明对p-q表示“p连接到q”。我们假设前提条件是这种连通性是可传递的:同理就是如果p和q之间连通,q和r之间连通,那么我们可以认为p和r是连通的。

我们的目标是通过写一个程序过滤集合中的无关对的程序。程序的输入为对p-q,如果已经看到那点的数对并不隐含着p连通到q,那么输出该对。如果前面的对确实隐含这p连通到q,那么程序应该忽略p-q,并应该继续输入下一对。

连通问题示例:
  3-4   3-4
  4-9   4-9
  8-0 8-0
  2-3   2-3
  5-6 5-6
  2-9  2-3-4-9
  ……
我们的问题是设计能够记录足够多的数对信息程序,并能够判定一个新的对象对是否是连通的,我们称设计这样一个算法的任务为连通性问题。
同时这个问题在生活上有着很大的应用,例如在网络节点的判断两个节点的连通与否、判断程序段的变量等价、判断电路线路是否短路或者城市线路的连通性等。
作为入门级别,我们选择先通过解决小规模的简单算法先行实现解这个问题的方法。
对于解决上述问题,在小规模情况下有如下解决方案:
1.连通问题的快速查找算法(quick-find algorithm)
  该算法的基础是一个整型数组,当且仅当第p个元素和第q个元素相等时,p和q是连通的。初始时,数组中的第i个元素的值为i,0<=i<N.为实现p和q的合并操作,我们遍历数组,把所有名为p的元素值改为q。我们同样也可以采用另一种方式,把所有名为q的元素改为p。
实现:
#include<stdio.h>
#define N 100
int main()
{
  int i,p,q,t,id[N];
  for(i=0;i<N;i++)  id[i]=i;
  //算法关键步骤
  whlie(scanf("%d %d\n",&p,&q)==2){
    if(id[p]==id[q])
      continue;
    for(t=id[p],i=0;i<N;i++){
      if(id[i]==t)id[i]=id[q];
    }
    printf("%d %d\n",p,q);
  }
  return 0;
}
对于此算法的理解:此算法思想是可以连通的归作一个类别(集合)。实现过程是将输入连接的对p-q,如果p,q里的值相等则说明已经是连接的,否则把p里的值保存下来进行循环遍历整个数组进行查找到跟p里的值一样的单元然后都替换为q里的值,此时就完成了相关连通性的录入。
上述为了是实现查找操作,只需要测试指示数组中的元素是否相等,因此称为快速查找。然而合并对于每对输入需要扫描整个数组。
性质:求解N个对象的连通性问题,如果执行M次合并操作,那么快速查找算法至少执行MN条指令。
 
2.连通问题的快速合并问题
  它基于同一个数据结构,即通过对象名引用数组元素,但数组元素表达的含有不同,具有更复杂的抽象结构。在一个没有环的结构中,每个对象指向同一集合中的另一个对象。要确定两个对象是否在同一个集合中,只需跟随每个对象的指针,直到达到指向自身的一个对象。当且仅当这个过程使两个对象到达同一个对象时,这两个对象在同一个集合中。如果两者不在同一个集合中,最终一定到达不同的对象(每个对象都指向自身)。为了构造合并操作,我们只需将一个对象链接接到另一个对象以便执行合并操作。因此命名为快速合并。
实现:

#define N 10
#include<stdio.h>
int main() {
  int i, p, q,id[N],j;
  for (i = 0; i < N; i++)
i    d[i] = i;
  while (scanf("%d %d\n", &p, &q) == 2) {
    for (i = p; i != id[i];i=id[i]);
    for (j = q; j != id[j];j=id[j]);
    if (i == j)
      continue;
    id[i] = j;
  printf("%d %d\n", p, q);
  }
  return 0;
}

  快速合并算法与快速查找算法之间的差异确实代表这一种改进,但是快速合并算法仍然具有局限性,我们不能保证每种情况下,它都会比快速查找算法要快,因为输入数据可能使查找操作变慢。

性质:对于M>N,快速合并算法求解N个对象、M个对的连通问题需要执行MN/2条指令。

假定输入对按照1-2,2-3,3-4,……的次序出现。N-1个这样的输入对之后,可得N个对象都在一个集合中,且快速合并算法形成的树是一条直线,其中N指向N-1,N-1指向N-2,……以此类推。要在对象N上执行查找操作,程序必须遍历N-1个指针。因此,对前N个对遍历的平均指针数(0+1+2+.....+(N-1)/N=(N-1)/2。

现如今假设其余对都把N连接到某个对象。每对进行查找操作至少访问N-1个指针。因而在这个输入对序列上执行M个查找操作访问的指针总数必定大于MN/2。

 

不过对于以上的那种糟糕情况,只需要进行简单的修改就就不会出现那种情况。在合并操作中,不是任意地把第二颗树连接到第一颗树上,而是记录每棵树的节点数,总是把较小的树连接到连接到较大的树上。下面给出改良算法:

#include<stdio.h>
#define N 100
int main() {
  int i, j, p, q, id[N], a[N];
  for (i = 0; i < N; i++) {
    id[i] = i;
    a[i] = 1;
  }
  while (scanf("%d %d\n", &p, &q) == 2) {
    for (i = p; i != id[i]; i = id[i]);
    for (j = q; j != id[j]; j = id[j]);
    if (i == j)continue;
    //只关注本节点上挂载的节点数与上一节点不需要考虑
    //这一段最好画个图动态思考一下
    if (a[i] < a[j]) {
      id[i] = j;
      a[j] += a[i];
    }
  else {
    id[j] = i;
    a[i] += a[j];
    }
   printf("%d %d\n", p, q);
  }
return 0;
}

得到以下结论:对于N个对象,加权快速合并算法在判断两个对象是否是连通的,最多需要遍历2lgN个指针对象。

对于上述问题还有改良方法,敬请期待!

 

  
 

 

posted @ 2022-11-10 22:57  sangGou  阅读(87)  评论(0编辑  收藏  举报