合并社区 - 使用集合数据结构

时间:2015-07-16 11:17:02

标签: java algorithm data-structures merge set

问题陈述

人们在社交网络中相互联系。人I和人J之间的联系表示为M I J.当属于不同社区的两个人联系时,净效应是我和J所属的两个社区的合并。

一开始,有N个人代表N个社区。假设人1和2连接,后来2和3连接,则1,2和3将属于同一社区。

有两种类型的查询:

M I J =>包含人I和J的社区合并(如果他们属于不同的社区)。

Q I =>打印我所属的社区的大小。

我的方法:

我创建了一组空集。当两个人合并时,我正在检查所有内部集合,如果找到任何内部集合,我将它们添加到该集合并突破。 如果不是,我正在与这些人创造一个新的内部集合。 现在在这个父集合中,我需要将所有内部集合相互比较,如果找到了交集,我应该组合两个内部集合,这是我无法做到的。

我的方法是否正确?但这是一个非常迭代的过程,有没有更好的方法来解决它?

我的代码:

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class Solution {

    public static void main(String[] args) {
        /* Enter your code here. Read input from STDIN. Print output to STDOUT. Your class should be named Solution. */
        Scanner sc = new Scanner(System.in);
        int nPeople = sc.nextInt();
        int queries = sc.nextInt();
        Set<Set<Integer>> community = new HashSet<Set<Integer>>();
        for(int i = 0 ; i<queries ; i++){
           char query = sc.next().charAt(0);
           if(query == 'Q'){
                int p = sc.nextInt();
                Set<Integer> tmpset = new HashSet<Integer>();

                for( Set<Integer> innerSet : community){
                   for(Integer person : innerSet) {
                       if( person == p ){
                           for(Integer each : innerSet){
                               tmpset.add(each);
                           }
                       }
                   }
                }

                if(tmpset.size()!= 0) {
                    System.out.println(tmpset.size());
                }
                else {
                    System.out.println("1");
                }
            }
            else if(query=='M'){
                int person1 = sc.nextInt();
                int person2 = sc.nextInt();

                int c = 0;

                loop:
                for( Set<Integer> innerSet : community){
                   for(Integer person : innerSet) {
                       if( person == person1 || person == person2){
                           innerSet.add(person1);
                           innerSet.add(person2);
                           c++;
                           break loop;

                       }
                   }
                }

                if(c==0){
                     Set<Integer> tmpset = new HashSet<Integer>();
                     tmpset.add(person1);
                     tmpset.add(person2);
                     community.add(tmpset);
                }
            }
        }


    }
}

我的代码输出:设置包含集。

enter image description here

问题链接:https://www.hackerrank.com/challenges/merging-communities

在@Adamski的帮助下解决了它,使用了不相交集数据结构,但仍然没有那么有效的解决方案。

代码:

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class Solution {

    public static void main(String[] args) {
        /* Enter your code here. Read input from STDIN. Print output to STDOUT. Your class should be named Solution. */
        Scanner sc = new Scanner(System.in);
        int nPeople = sc.nextInt();
        DisJoint comm = new DisJoint(nPeople);
        int queries = sc.nextInt();
        for(int k = 0 ; k<queries ; k++){
            char query = sc.next().charAt(0);
            if(query == 'Q'){
               int person = sc.nextInt(); 
               int personParent = comm.Find(person);
               int community = 0;
               for(int j =1 ; j<nPeople+1 ; j++){
                   int tmpParent = comm.Find(j);
                   if(personParent == tmpParent){
                       community++;
                   }
               }
               System.out.println(community);
            }
            if(query == 'M'){
                int person1 = sc.nextInt();
                int person2 = sc.nextInt();
                comm.Union(person1,person2);
            }
        }

    }
}

 class DisJoint{
    public int Count;
    public int[] Parent;
    public int[] Rank;
    public DisJoint(int count){
        this.Count = count;
        this.Parent = new int[this.Count+1];
        this.Rank = new int[this.Count+1];
        for (int i = 1; i < this.Count+1; i++) {
            this.Parent[i] = i;
            this.Rank[i] = 0;
        }
    }
    public int Find(int i){
        if(i == Parent[i]){
            return Parent[i];
        }
        else{
           int result = Find(Parent[i]);
           Parent[i] = result;
           return result;
        }
    }

    public void  Union(int a, int b){
        if(a>b){
            int tmp = a;
            a = b;
            b = tmp;
        }
        int aroot = this.Find(a);
        int broot = this.Find(b);
        int arank = Rank[aroot];
        int brank = Rank[broot];

        if (aroot == broot){
           return;
        }
        if (arank < brank) {
           this.Parent[aroot] = broot;
         } 
        else if (arank > brank) {
          this.Parent[broot] = aroot;
         }
        else{
          this.Parent[aroot] = broot;
          Rank[broot]++;
        }
    }

}

请测试上述链接中的代码。

4 个答案:

答案 0 :(得分:2)

您是否考虑过使用数据结构来表示不相交的集合?例如:http://www.mathblog.dk/disjoint-set-data-structure/

基本前提是您定义一个类(例如Person)并将您的社区集表示为单个数组。每个Person都包含一个返回数组的索引,指向自身或指向另一个Person

| Adam | Dave | Fred | Tom | James |
| 0    | 0    | 1    | 3   | 3     |

在上面的例子中,为了测试Fred和Dave是否在同一个社区中,你从每个人开始并按照图表到根人;即索引引用自己的人:

Fred -> Dave -> Adam
Dave -> Adam

(显然这里有一个优化,在第一次遍历期间,你在到达根之前实际遇到Dave。)

相比之下,测试詹姆斯和弗雷德是否在同一社区:

James -> Tom
Fred -> Dave -> Adam

人们有不同的根源,因此属于不同的社区。

合并社区就是将一个社区的根人重新分配给另一个社区的根。

推断社区规模更为复杂;我会把这作为练习让你弄清楚!

答案 1 :(得分:0)

首先,关于你的代码。这是糟糕的OO设计:

  1. 一切都在一种方法中(您是否考虑为'Q'和'M'制作单独的方法?)
  2. 所有变量都是该方法的本地变量
  3. 现在,关于解决方案,我采取了另一种方法:

    1. 一个人总是只属于一个社区。所以我有一个“世界”数据结构,能够直接告诉我任何指定人的社区。它可以是一个地图,其中关键是人和社区是有价值的。我选择了一个List,其中索引是person,value是community。
    2. 一旦你有了这个,很容易找到任何指定人的社区(我有一个特定的方法可以做到这一点)
    3. 一旦你有了找到一个社区的能力,Q'操作变成一个给定的(列表的大小,duh)并且与“world”'M'操作一起也很容易(猜猜看,我有一个特定的方法做就是那个......)
    4. 所以我创建了以下代码(测试和工作) 注意:因为在问题中,索引从1开始,我用size == nPeople定义了“world”,并且没有使用它的0索引。

      public class Solution
      {
          // data structure: index is person and value is community
          static List<Integer> world;
      
          // input 
          static int nPeople, queries;
          static Scanner sc = new Scanner(System.in);
      
          public static void main(String[] args)
          {
              /* Enter your code here. Read input from STDIN. Print output to STDOUT. Your class should be named Solution. */
              System.out.println("people operations:");
              init();
      
              for (int i = 0; i < queries; i++) {
                  System.out.println("next operation:");
                  char operation = sc.next().charAt(0);
                  switch (operation) {
                  case 'Q':
                      System.out.println(query(sc.nextInt()));
                      break;
                  case 'M':
                      merge(sc.nextInt(), sc.nextInt());
                      System.out.println("done");
                      break;
                  }
              }
          }
      
          // init world
          private static void init()
          {
              nPeople = sc.nextInt();
              queries = sc.nextInt();
              world = new ArrayList<>();
              for (int i = 0 ; i <= nPeople ; i++)  world.add(i);
          }
      
          // get the community of specified person 
          // return size
          private static int query(Integer person)
          {
              return getCommunity(person).size();
          }
      
          // check that the two specified persons do not belong to same community 
          // get the two communities of specified persons 
          // iterate over list of persons of comunity2, 
          // set them to community (value) of person1 
          private static void merge(Integer person1, Integer person2)
          {
              if (world.get(person1).equals(world.get(person2))) return;
      
              List<Integer> community1 = getCommunity(person1);
              List<Integer> community2 = getCommunity(person2);
              if (community1.isEmpty() || community2.isEmpty()) return;
      
              Integer community1Id = world.get(community1.get(0)); 
              for (Integer person : community2) {
                  world.set(person, community1Id);
              }
          }
      
          // returns list of persons (indexes in world) 
          // that belong to community (value) of specified person
          private static List<Integer> getCommunity(Integer person)
          {
              List<Integer> community = new ArrayList<>();
              for (int i = 0 ; i < world.size() ; i++) {
                  if (world.get(i).equals(world.get(person))) {
                      community.add(i);
                  }
              }
              return community;
          }
      }
      

答案 2 :(得分:0)

我已经找到了问题的完整解决方案。谢谢大家。没有你的帮助,我无法做到这一点。所有测试用例都被接受了。

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class Solution {
static int parent[],rank[],m[];
static int find(int i)
{
    if(i==parent[i])
    return i;
   int res=find(parent[i]);
   parent[i]=res;
        return res;

}

static void union(int i,int j)
{
    int irep=find(i),jrep=find(j);
    if(irep==jrep)
    return;
    if(rank[irep]>rank[jrep])
    {
        parent[jrep]=irep;
        m[irep]+=m[jrep];
    }
    else if(rank[irep]<rank[jrep])
    {
        parent[irep]=jrep;
        m[jrep]+=m[irep];
    }
    else
    {
        parent[jrep]=irep;
        rank[irep]++;
        m[irep]+=m[jrep];
    }



}
public static void main(String []args)throws IOException
{
    Scanner sc=new Scanner(System.in);
    int n=sc.nextInt(),q=sc.nextInt();
parent=new int[n+1];
rank=new int[n+1];
m=new int[n+1];
for(int i=0;i<=n;i++)
    {
    parent[i]=i;
    rank[i]=1;
    m[i]=1;

}
for(int i=0;i<q;i++)
    {

    switch(sc.next().charAt(0))
        {
        case 'Q':
        int c=0;

        System.out.println(m[find(sc.nextInt())]);
        break;
        case 'M':
        union(sc.nextInt(),sc.nextInt());
        break;

    }

}

}



}

答案 3 :(得分:0)

P = {P1,...,Pn}成为一群人。由于尚未连接,因此每个社区的大小为1

P1 -> Community size = 1
P2 -> Community size = 1
P3 -> Community size = 1
...
Pn -> Community size = 1

如果我们合并P1P2,那么P2可以跟随P1,因此它们都成为同一个社区的一部分。为了清楚起见-P2跟在P1之后,因为P1的社区规模并不小。

P2 -> P1 -> Community size = 2
P3 -> Community size = 1
...
Pn -> Community size = 1

因此,现在,如果我们检查P2属于哪个社区,我们会发现一个跟随P2的人-它是P1。但是,由于P1没有跟随其他任何人,因此P2属于P1(每个社区都由其关注最多的成员定义)。

如果我们合并P2P3,则随着P3社区规模变小,他必须遵循与P2相同的跟随者。由于P1P2的关注最多的人,因此P3开始关注P1,因此成为P1的一部分。

P2 -> P1 -> Community size = 3
      ^
      |
      P3      
...
Pn -> Community size = 1

我们可以进一步继续该过程,但是现在很明显,此配置可以表示为一个数组(根据问题描述,人数限制为100,000),其中索引对应于一个人,其值对应于其关注的最高社区会员。

另一个数组可以代表每个人的社区大小(索引->人,值->大小)。但是,我们将仅针对关注人数最多的社区而不是社区的每个成员进行更新,因为我们始终可以根据任何成员联系高层社区,从而找到社区规模。

然后,在任何2个人PiPj的每次合并过程中,我们发现其跟随的最高代表-topPitopPj。如果它们相同-没有理由进行合并,因为PiPj已经属于同一社区。如果不是,请加入更大的社区,并相应地更新其大小。

要显示任何人的社区规模,请找到其最高的关注者代表(递归转到当前关注者的下一个人),并获取该代表者社区的规模。

现在输入一些代码。

最初,每个人都在一个单独的社区中。人和社区由相同的正整数值标识。每个社区的大小为1

int people[100001];
int sizes[100001];
void init(int n) {
    for (int i = 1; i <= n; i++) {
        people[i] = i;
        sizes[i] = 1;
    }
}

要找到给定人员关注度最高的社区,我们需要一个功能:

int findTopFollowedPerson(int x) {
    if (people[x] != x) {
        return findTopFollowedPerson(communities[x]);
    }
    return x;
}

要合并2x的任何y个人,请提供一种merge方法:

void mergeCommunities(int x, int y) {
    x = findTopFollowedPerson(x);
    y = findTopFollowedPerson(y);
    if (x == y) {
        return;
    }

    if (sizes[x] >= sizes[y]) {
        people[y] = x;
        sizes[x] += sizes[y];
    } else {
        people[x] = y;
        sizes[y] += sizes[x];
    }
}

然后,要检查任何人的社区的大小,请返回跟随人数最多的人的社区的大小:

int communitySize(int x) {
    int root = findTopFollowedPerson(x);
    return sizes[root];
}