算法学习——1.连通性问题(一)

参考:Robert Sedgewick,《算法:C语言实现》第1章

1.1算法

我们把数据结构看作是算法的副产品或者最终产物,研究这些数据结构从而以便理解算法。简单的算法可以导致非常复杂的数据结构,反之,复杂算法可以利用简单的数据结构。

1.2 典型问题-连通性问题

要点:如果需求需要输出N个对象(整数)中,存在连接的数对。那么最多只能输出N-1个数对。如果能够输出N-1个数对,那说明给定的所有对象都是连通的

1.2.1 快速查找算法

利用整数数组,每个整数对应一个对象,使用数组下标表示新建的数对。

通俗理解:数组a[N],使用自然数列赋值,表示有N台电脑(对象),大家都不相等,则所有电脑之间都没有连接。数值相等则表示有链接!!! 输入数对1-2,就是询问电脑1和电脑2有没有连接。

算法实现方法:输入数对1-2,就表示电脑1和电脑2新建连接。并把电脑2的值(数据)赋值给电脑1。数值相等则表示有链接!!!当再次输入1-2时,判断a[1] == a[2],即表示1-2已经建立了连接!!

#include <stdio.h>

#define N (1000)

int main()
{
     int i, p, q, t;
     int id[N];
     //自然数列,所有对象的数值互不相等,则表示大家之间都没有连接
     for (i = 0; i < N; i++) 
     {
         id[i] = i;
     }

     //循环读入整数对
     while (scanf("%d-%d", &p, &q) == 2)
     {
         //如果对象p与q是连通的,则读取下一对数对
         if (id[p] == id[q]) 
             continue;

         //如果id[p]与id[q]的值不相等,则说明p-q是新对,就是没有连接
         //则将所有原本与id[p]元素值相等的所有元素连接到q,即建立连接
         for (t = id[p], i = 0; i < N; i++)
         {
             if (id[i] == t)
                 id[i] = id[q];
         }
         //因为p-q是新对,所以输出这个对
         printf("New pair: %d-%d\n", p, q);
     }

     return 0;
 }

运行结果

可以通过具体的数据变化来查看算法的实现过程。

可以看出,为了实现查找操作,只需测试指定数组元素的值是否相等就行,因此称之为快速查找;而合并操作对于每一对输入都需要遍历整个数组元素,因此为慢速合并

求解N个对象的连通性问题,如果执行M次合并操作,那么快速查找算法至少要执行M*N条指令。

1.2.2 快速合并算法

#include <stdio.h>

#define N (10)

int main()
{
     int i, p, q, j;
     int id[N];
     //初始化对象集合中元素的初始值
     for (i = 0; i < N; i++) id[i] = i;
     //循环读入整数对
     while (scanf("%d-%d", &p, &q) == 2)
     {
         //从位置P读取值,即读取p的根节点
         for (i = p; i != id[i]; i = id[i]);

         for (j = q; j != id[j]; j = id[j]);

         if (i == j)
         {
             //i、j 位置对象的值相等则是已存在的连接
             //即p和q的根节点相同
             continue;
         }
         else
         {
             //不相等则说明是新连接
             id[i] = j;
             //因为p-q是新对,所以输出这个对
             printf("New pair: %d-%d\n", p, q);
         }
     }

     return 0;
 }

通过两个for循环,去查找数对p和q的根节点,并合并该节点。

通俗理解:假设原始数组为a[10] =  0 1 2 3 4 5 6 7 8 9 ,  数组元素的值,就表示这些对象(电脑)的上一个节点。初始情况是0~9各不相同。就表示大家的上一个节点都是自己本身,,都不一样,当前不存在连接。

当第一次输入1-2时,则去比较对象1和对象2的根节点是否相同,发现不相同(根节点不同)说明是新建连接。之后并把对象2的根节点值(2)赋值给对象1,表示对象1现在的根节点是对象2。则当前的数组为a[10] = 0 2 2 3 4 5 6 7 8 9

当第二次输入2-3时,对象2对象3的根节点也不相同,为新连接,与上面的操作相同。但是与快速查找法不同的是,现在只赋值对象2的根节点,而不去赋值对象1的根节点。则当前的数组为a[10] = 0 2 3 3 4 5 6 7 8 9

当第三次输入1-3时,for循环语句

for (i = p; i != id[i]; i = id[i]);

,会循环向上查询上一个节点,直到查询到根节点(最上面的节点)。对象1的上一个节点(对象1的值)是2,然后对象2的上一个节点是3,此时对象3就是对象1的根节点了,退出循环。查找对象3的根节点,就很简单就是他本身。那么1和3具有相同的根节点,则说明是已有连接。此时的数组数据不变。

当第四次输入3-9时,数组为a[10] = 0 2 3 9 4 5 6 7 8 9

当第五次输入1-9时,则原本对象1的根节点对象3,又有了新的根节点对象9,则1-9仍然是已有连接。通过这样的方式,还可以变相输出连接顺序(代码贴在1.2.2.1)。

快速合并法是快速查找法的一个优化算法,因为对于每个输入它并不是总要遍历整个数组。它去掉了快速查找算法的主要局限性(对N个对象执行M次合并操作,程序至少需要M*N条指令)。

1.2.2.1 快速合并法基础上 输出连接顺序

#include <stdio.h>

#define N (10)

int main()
{
     int i, p, q, j;
     int id[N];
     int sn[N], t;
     //初始化对象集合中元素的初始值
     for (i = 0; i < N; i++) id[i] = i;
     //循环读入整数对
     while (scanf("%d-%d", &p, &q) == 2)
     {
         for (t=0; t<N; t++) sn[t]=0;
         t=0;

         //从位置P读取值,即读取p的根节点
         for (i = p; i != id[i]; i = id[i])
         {
             sn[t++] = i;
         }

         for (j = q; j != id[j]; j = id[j]);

         if (i == j)
         {
             sn[t] = j;
             printf("Old pair: ");
             for(t=0; sn[t]!=0; t++)
             {
                 printf("%d-", sn[t]);
             }
             printf("\n");
             //i、j 位置对象的值相等则是已存在的连接
             //即p和q的根节点相同
             continue;
         }
         else
         {
             //不相等则说明是新连接
             id[i] = j;
             //因为p-q是新对,所以输出这个对
             printf("New pair: %d-%d\n", p, q);
         }
     }
 
     return 0;

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值