联盟发现表达为社交网络

时间:2014-09-12 01:46:49

标签: algorithm union-find

这是我想回答的面试问题:

  

给定一个包含N成员的社交网络和一个包含M个时间戳的日志文件,成员对形成友谊,设计一个算法来确定所有成员连接的最早时间(即,每个成员都是朋友的朋友......朋友的朋友。假设日志文件按时间戳排序,并且友谊是等价关系。算法的运行时间应为M log N或更高,并使用与N成比例的额外空间。

我想到的第一件事就是......"我不能这样做!"。

但后来我想这个社交网络怎么能表达为数据结构。 Union-find是一种可以使用的数据结构。现在我必须了解所有成员连接时的含义。如何查看实际数据结构以及每个成员彼此成为朋友时的样子?

我认为只有在我能够从视觉上或概念上理解系统如何完全连接之后我才能开始弄清楚如何找到与该事件相对应的时间戳。

9 个答案:

答案 0 :(得分:17)

当您向union-find数据结构添加友谊时,您可以注意它是否会导致连接两个图形组件。只需继续添加边缘,直到发生这些合并事件的N-1。

以伪代码形式:

G := UnionFind(1..N)
count := 0
for timestamp, p1, p2 in friendships {
    if G.Find(p1) != G.Find(p2) {
        G.Union(p1, p2)
        count++
        if count == N-1 {
            return timestamp
        }
    }
}
return +infinity

答案 1 :(得分:14)

好的,为了解决这个问题,我假设日志文件看起来像这样:

0 1 2015-08-14 18:00:00
1 9 2015-08-14 18:01:00
0 2 2015-08-14 18:02:00
0 3 2015-08-14 18:04:00
0 4 2015-08-14 18:06:00
0 5 2015-08-14 18:08:00
0 6 2015-08-14 18:10:00
0 7 2015-08-14 18:12:00
0 8 2015-08-14 18:14:00
1 2 2015-08-14 18:16:00
1 3 2015-08-14 18:18:00
1 4 2015-08-14 18:20:00
1 5 2015-08-14 18:22:00
2 1 2015-08-14 18:24:00
2 3 2015-08-14 18:26:00
2 4 2015-08-14 18:28:00
5 5 2015-08-14 18:30:00

2个第一个数字是形成友谊的成员,其后是时间戳。

另一个重要的事情是,练习提到文件已经排序,所以我决定按升序排序。

使用此信息,您可以使用类中提供的WeightedQuickUnionFind数据结构,并简单地处理对成员执行联合操作的文件,一旦您创建了联合,您可以询问有多少组件在结构,如果只有一个意味着所有成员都有equivalent relation

这是我做的代码:

public static void main(String[] args) {

        int n = StdIn.readInt();
        WeightedQuickUnion uf = new WeightedQuickUnion(n);
        String date, time;
        //timestamps are sorted ascending
        while (!StdIn.isEmpty()) {

            int p = StdIn.readInt();
            int q = StdIn.readInt();
            date = StdIn.readString();
            time = StdIn.readString();


            uf.union(p, q);

            StdOut.println("["+p+","+q+"]");

            if(uf.getComponents() == 1){
                StdOut.println("All members were connected at: " + date + time);
                break;
            }

        }

性能将是 M lg N ,因为您正在迭代 M 次(日志文件中的行数)和联合操作需要: lg n

答案 2 :(得分:2)

要确定所有成员是否都已连接,我使用了加权Quick-union的概念。如果树的大小等于n,则可以说所有成员都已连接。 我的代码:

class MyClas {
    private int[] a;
    private int[] size;
    int N=0;
    public MyClas(int n){
        N=n;
        a = new int[n];
        size = new int[n];
        for(int i=0;i<n;i++){
            a[i]=i;
            size[i]=1;
        }
    }
    private int root(int x){
        while(x != a[x]){
            x=a[x];
        }
        return x;
    }
    public boolean connected(int p, int q){
        return root(p)==root(q);
    }
    public void union(int p,int q, String timestamp){
        int i = root(p);
        int j = root(q);
        if(i == j) return;
        if(size[i] < size[j]){
            a[i] = j;
            size[j]+=size[i];
            if(size[j]==N){
                System.out.println("All Members are connected at Timestamp"+ timestamp);
            }
        }
        else{
            a[j] = i;
            size[i]+=size[j];
            if(size[i]==N){
                System.out.println("All Members are connected at Timestamp"+ timestamp);
            }
        }
    }

}
public class MyClass {
    public static void main(String args[]) {
      MyClas obj = new MyClas(6);
      obj.union(1,5, "2019-08-14 18:00:00");
      obj.union(2,4, "2019-08-14 18:00:01");
      obj.union(1,3, "2019-08-14 18:00:02");
      obj.union(5,2, "2019-08-14 18:00:03");
      obj.union(0,3,"2019-08-14 18:00:04");
      obj.union(2,1,"2019-08-14 18:00:05");

    }
}

答案 3 :(得分:0)

Andres提供的答案是正确的。还有另一种方式。你可以在这里使用N个元素的额外空间。因此,在合并两个树之后,您可以在树和节点函数中保留一组节点总数,您必须将要合并的树中的节点数添加到树中的节点数(即根节点)两者。因此,在添加之后,您只需检查新树中的节点总数是否等于N.如果是,那么它将是所有N个成员彼此成为朋友的最早时间。

答案 4 :(得分:0)

添加到@Andrés的答案。以下是检查所有这些是否已连接的方法,将在WeightedQuickUnionFind类中添加。

public boolean isAllConnected(){
    int N = id.length;
    while(--N>0 && id[N] == id[0]);
    return N == 0;
}

答案 5 :(得分:0)

社交网络可以表示为树或图,其中每个节点具有0到n个连接。

您需要的数据结构的主要部分是一个整数数组,其中每个元素索引都可以解释为社交网络成员ID,而值是另一个成员的ID,该成员的第一个成员是root。

您需要阅读日志并在每个日志记录的数据结构中执行union操作(构建树)并分析两个条件

  • 每个成员都有一个连接(Set<Integer> haveConnection
  • 只有一个global根(因为从一开始,您将有很多具有自己根的未连接子网)(Set<Integer> roots

两个条件都满足-网络中的所有成员都已连接。

祝你好运!

答案 6 :(得分:0)

可以通过添加连接的组件数量的计数来轻松完成此操作。将N个成员视为N个对象,将M个时间戳视为M个并集。那么“所有成员的最早连接时间”是连接的组件数等于1的时间。

答案 7 :(得分:0)

将我的意见添加到@AndrésSoto。总体来说,我同意his answer,但我认为我们不需要getComponent()方法。

在WeightedUnionFind中,请记住,我们有一个数组size,我们需要比较两个组件的大小,以便确定哪个组件将哪个组件。

    public class WeightedQuickUnionUF {
        private int[] parent;   // parent[i] = parent of i
        private int[] size;     // size[i] = number of elements in subtree rooted at i
        private int count;      // number of components

每次有效结合后,我们将更新大小。因此,我们只需要判断更新的大小是否等于N。如果是,则此步骤是所有人员都已连接的步骤。

答案 8 :(得分:0)

安德烈斯·索托(Andres Soto)和莫尼尔(Monil)的两个答案在这里看起来都很有效。

我想指出的一件事是,它看起来像Kruskal's algorithm-即,找到了无向边加权图的最小生成树/森林(MST)。基本上,有成员(图的顶点)和时间戳(顶点之间的加权边)-所谓的连接图。由于得到了排序列表,因此该算法非常适合-您使用加权快速联合(甚至使用路径压缩)并跟踪每棵树的大小以识别MST(请检查Monil答案)。

注意:如果图形断开连接,则最小生成树由每个组件/树的最小生成树组成。

UPD:注意到本杰明在同样提到该算法的问题下方的评论。

相关问题