并差集的链表、路径压缩两种方法

Discription

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

Input

第一行:三个整数n,m,p,(n< =5000,m< =5000,p< =5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。 以下m行:每行两个数Mi,Mj,1< =Mi,Mj< =N,表示Mi和Mj具有亲戚关系。 接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。

Output

P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

算法

算法需要以下几个子过程:
(1) 开始时,为每个人建立一个集合SUB-Make-Set(x);
(2) 得到一个关系后a,b,合并相应集合SUB-Union(a,b);
(3) 此外我们还需要判断两个人是否在同一个集合中,这就涉及到如何标识集合的问题。我们可以在每个集合中选一个代表标识集合,因此我们需要一个子过程给出每个集合的代表元SUB-Find-Set(a)。于是判断两个人是否在同一个集合中,即两个人是否为亲戚,等价于判断SUB-Find-Set(a)=SUB-Find-Set(b)。
有了以上子过程的支持,我们就有如下算法。

PROBLEM-Relations(N, M, a1,…,aM, b1,…,bM, Q, c1,…,cQ, d1,…,dQ)
1 for i←1 to N
2 do SUB-Make-Set(i)
3 for i←1 to M
4 do if SUB-Find-Set(ai) != SUB-Find-Set(bi)
5 then SUB-Union(ai, bi)
6 for i←1 to Q
7 do if SUB-Find-Set(ci)=SUB-Find-Set(di)
8 then output “Yes?”
9 else output “No?”

  • 单链表实现
    一个节点对应一个人,在同一个集合中的节点串成一条链表就得到了单链表的实现。在集合中我们以单链表的第一个节点作为集合的代表元。于是每个节点x(x也是人的编号)应包含这些信息:指向代表元即表首的指针head[x],指向表尾的指针tail[x],下一个节点的指针next[x]。
    SUB-Make-Set(x)过程设计如下:
    SUB-Make-Set(x)
    10 head[x]←x
    11 tail[x]←x
    12 next[x]←NIL
    求代表元的SUB-Find-Set(x)过程设计如下:
    SUB-Find-Set(x)
    13 return head[x]
    前两个过程比较简单,SUB-Union(a,b)稍微复杂一点。我们要做的是将b所在链表加到a所在链表尾,然后b所在链表中的所有节点的代表元指针改指a所在链表的表首节点,如图所示。
    图0-0-2
    过程的伪代码如下:
    SUB-Union(a,b)
    14 next[tail[head[a]]]←head[b]
    15 tail[head[a]]←tail[head[b]]
    16 p←head[b]
    17 while p != NIL
    18 do head[p]←head[a]
    19 p←next[p]

  • 优化——路径压缩
    思想
    每次查找的时候,如果路径较长,则修改信息,以便下次查找的时候速度更快。
    实现
    第一步,找到根结点。
    第二步,修改查找路径上的所有节点,将它们都指向根结点。

    
    #include <iostream>
    
    using namespace std;
    
    int father[50002],a,b,m,n,p;
    int find(int x)
    {
       if(father[x] != x)
          father[x] = find(father[x]);
          /*x代表例题中的人,
    都与一个人有亲戚关系,相当于例题中第一个集合所有的元素都与第一个元素有亲戚关系,
    搜索时只要找元素所指向的father[x]=x的元素(即父元素),
    然后比较两个元素的父元素是否相同就可以判断其关系
    */
    
       return father[x];
       //也可以修改成 return father[x] == x ? x : father[x] = find(father[x]);
    }
    
    int main()
    {
       int i;
       scanf("%d%d%d",&n,&m,&b);
       for(int i = 1;i <= n;i++)
           father[i] = i;
    
     for(i=1;i<=m;i++)
    {
      scanf("%d%d",&a,&b);
      a=find(a);
      b=find(b);
      father[a]=b;
    }
    for(i = 1;i <= p;i++)
    {
            scanf("%d%d",&a,&b);
            a = find(a);
            b = find(b);
            if(a == b)
               printf("Yes\n");
            else
               printf("No\n");
    }
        return 0;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值