简体   繁体   English

给定图中的连接,找到所有连接的组件(Java)

[英]Find all connected components given the connections in a graph (Java)

I'm building a program in Java that simulates a network of moving nodes. 我正在用Java构建一个程序来模拟移动节点的网络。 When nodes collide there is a certain probability that they will establish a connection. 当节点冲突时,它们很有可能会建立连接。 I'm trying to store all the nodes in the graph in a way that makes the connected components obvious, so I'm storing it as a Set of Lists where each list is a component and the set contains all components. 我试图以一种使连接的组件显而易见的方式存储图形中的所有节点,所以我将其存储为一组列表,其中每个列表都是一个组件,而该集合包含所有组件。 (Each component must have size greater than 1.) (每个组件的大小必须大于1。)

The complete code is on github: https://github.com/IceCreamYou/ViralSpread 完整的代码在github上: https : //github.com/IceCreamYou/ViralSpread

But here's the important part: 但这是重要的部分:

    // Store the components in the "temp" variable while we calculate them.
    // Each list is a component; the set contains all components.
    HashSet<LinkedList<Person>> temp = new HashSet<LinkedList<Person>>();
    // hardConnections is a set of all connections between the nodes.
    // Each connection appears only once (e.g. if there is a connection from A to B
    // there will not be a connection from B to A -- however each connection is
    // considered bidirectional).
    for (Person[] connection : hardConnections) {
        // Each element of hardConnections is an array
        // containing two connected nodes.
        // "Person" is the node class.
        Person a = connection[0], b = connection[1];
        boolean bFound = false, aFound = false;
        // If we've already added one of the nodes to a component,
        // add the other one to it.
        for (LinkedList<Person> component : temp) {
            boolean bInComponent = false, aInComponent = false;
            for (Person p : component) {
                if (p.samePosition(b)) {
                    bInComponent = true;
                }
                if (p.samePosition(a)) {
                    aInComponent = true;
                }
            }
            if (bInComponent && !aInComponent) {
                component.add(a);
                bFound = true;
                break;
            }
            else if (!bInComponent && aInComponent) {
                component.add(b);
                aFound = true;
                break;
            }
            else if (bInComponent && aInComponent) {
                aFound = true;
                bFound = true;
                break;
            }
        }
        // If neither node is in a component, create a new component containing both nodes.
        if (!bFound && !aFound) {
            LinkedList<Person> newComponent = new LinkedList<Person>();
            newComponent.add(b);
            newComponent.add(a);
            temp.add(newComponent);
        }
    }
    components = temp;

p.samePosition() checks if the nodes have the same X and Y position. p.samePosition()检查节点是否具有相同的X和Y位置。 I'm using that method instead of equals() just in case the problem is related to equality checks failing somehow, even though I haven't overridden anything in the Person class (including equals(), hashCode(), etc.). 我使用了该方法而不是equals(),以防万一该问题与相等性检查失败有关,即使我没有重写Person类中的任何内容(包括equals(),hashCode()等)。

This seems to work most of the time. 这似乎在大多数时间都有效。 Unfortunately sometimes after a new connection is added to a component from an existing component, this code calculates the new single component as being two components of incorrect sizes. 不幸的是,有时在将新连接从现有组件添加到组件后,此代码会将新的单个组件计算为大小不正确的两个组件。

I'm aware that the problem could also be that I'm calculating the connections incorrectly. 我知道问题也可能是我计算连接错误。 However, the connections display correctly on the screen, and there are the right number of them (in the set -- not just from counting on the screen) so the error is probably not there. 但是,连接正确地显示在屏幕上,并且连接数正确(在集合中-不仅仅是在屏幕上计数),因此错误可能不存在。 Just in case though, here's the code that calculates the connections. 为了以防万一,这里是计算连接的代码。 It's a little less generic than the code above so probably harder to read. 它的通用性比上面的代码少,因此可能更难阅读。

    // "infected" and "infector" are the nodes.
    // sameColor is true if the nodes have the same type.
    // Nodes with the same type should retain their connections
    // (which are also to nodes of the same type) while if the nodes have different types
    // then the "infected" node will lose its connections because
    // it will no longer be the same type as those connections.
    // Note that nodes of different types should never have connections to each other.
    boolean sameColor = (infected.getVirus().equalsVirus(infector.getVirus()));
    boolean sameColorConnectionExists = false;
    // As above, hardConnections is a set of connections between nodes (the Person class)
    Iterator<Person[]> it = hardConnections.iterator();
    while (it.hasNext()) {
        Person[] connection = it.next();
        if (sameColor) {
            if ((infected.equals(connection[0]) && infector.equals(connection[1])) ||
                    (infected.equals(connection[1]) && infector.equals(connection[0]))) {
                sameColorConnectionExists = true;
            }
        }
        // Remove connections to the connected person.
        else if (infected.equals(connection[0]) || infected.equals(connection[1])) {
            it.remove();
        }
    }

    // Create a new connection if the nodes were of different types or
    // if they are the same type but no connection exists between them.
    if (!sameColor || !sameColorConnectionExists) {
        hardConnections.add(new Person[] {infected, infector});
    }
    // After this function returns, the infected node is changed to the type of the infector.

Basically what's happening there is we walk through the set of connections and remove any connections to the infected node that aren't from nodes of the same type. 基本上,正在发生的事情是我们遍历连接的集合,并从同类型的节点中删除所有与受感染节点的连接。 Then if a connection between the infected and infecting nodes doesn't exist, we add one. 然后,如果受感染节点与感染节点之间不存在连接,则添加一个。

I can't figure out where the bug is here... any help (or even other tips / comments on the project itself) would be appreciated. 我不知道错误在哪里...任何帮助(甚至项目本身的其他技巧/评论)都将不胜感激。 If you're looking at the code in github, the code in question is in Statistics.java functions updateConnections() and updateComponents(). 如果您在github中查看代码,则有问题的代码在Statistics.java函数updateConnections()和updateComponents()中。 updateConnections() is called from Person.java in transferVirus(). 在transferVirus()中从Person.java调用updateConnections()。

Thanks. 谢谢。


UPDATE 更新

I've been outputting debugging information in an attempt to figure out what's going on. 我一直在输出调试信息,以试图找出正在发生的事情。 I've isolated one collision that causes incorrect fragmentation. 我隔离了一个会导致不正确碎片的碰撞。 In the data below, the first part shows the edges of the graph, and the second part shows the connected components and how many nodes are in them. 在下面的数据中,第一部分显示图形的边缘,第二部分显示连接的组件以及其中有多少个节点。 As you can see, after the second collision, there is an extra "dark gray" component that shouldn't be there. 如您所见,在第二次碰撞之后,不应该再有一个额外的“深灰色”组件。 There is only one "dark gray" component and it has 4 nodes in it. 只有一个“深灰色”组件,并且其中有4个节点。 (Screenshot: http://screencast.com/t/f9PIzjuk ) (屏幕截图: http : //screencast.com/t/f9PIzjuk

----
blue:(489, 82)          blue:(471, 449)
blue:(416, 412)         blue:(471, 449)
pink:(207, 172)         pink:(204, 132)
dark gray:(51, 285)     dark gray:(635, 278)
dark gray:(635, 278)    dark gray:(746, 369)
dark gray:(51, 285)     dark gray:(737, 313)
dark gray:(737, 313)    dark gray:(635, 278)
cyan:(2, 523)           cyan:(473, 273)

3 blue
2 pink
4 dark gray
2 cyan
----
blue:(514, 79)          blue:(535, 435)
dark gray:(725, 326)    dark gray:(717, 365)
blue:(404, 404)         blue:(535, 435)
pink:(197, 186)         pink:(236, 127)
dark gray:(35, 283)     dark gray:(619, 271)
dark gray:(619, 271)    dark gray:(717, 365)
dark gray:(35, 283)     dark gray:(725, 326)
dark gray:(725, 326)    dark gray:(619, 271)
cyan:(1, 505)           cyan:(490, 288)

3 blue
2 pink
4 dark gray
2 dark gray
2 cyan
----

After thinking more about what information I needed in order to debug this without producing an overwhelming amount of information, I produced this result of a snapshot during the simulation: 在仔细考虑了我需要什么信息以进行调试而又不产生大量信息之后,我在模拟过程中生成了快照的结果:

red:(227, 344)      red:(257, 318)
red:(643, 244)      red:(437, 140)
red:(437, 140)      red:(257, 318)
orange:(573, 485)       orange:(255, 143)
orange:(255, 143)       orange:(20, 86)
red:(227, 344)      red:(437, 140)
orange:(727, 494)       orange:(573, 485)

3 red
    red:(257, 318)
    red:(227, 344)
    red:(437, 140)
4 orange
    orange:(255, 143)
    orange:(573, 485)
    orange:(20, 86)
    orange:(727, 494)
2 red
    red:(437, 140)
    red:(643, 244)

If you follow the logic here: 如果您遵循此处的逻辑:

  • Look at the first connection. 查看第一个连接。 There are no components yet, so create a new one with red:(227, 344), red:(257, 318) . 还没有组件,因此请使用red:(227, 344), red:(257, 318)创建一个新组件。
  • Look at the second connection. 查看第二个连接。 There is only one component and none of the nodes in the component match either of the nodes in the connection, so create a second component with red:(227, 344), red:(257, 318) . 只有一个组件,并且该组件中的任何节点都不匹配连接中的任何一个节点,因此请使用red:(227, 344), red:(257, 318)创建第二个组件。 This is wrong because there are other nodes later in the list that connect the nodes in the current connection to the original component. 这是错误的,因为列表中稍后还有其他节点将当前连接中的节点连接到原始组件。
  • Look at the third connection. 查看第三个连接。 The second node matches a node in the first component, so add the first node to the first component. 第二个节点与第一个组件中的一个节点匹配,因此将第一个节点添加到第一个组件中。
  • ...and so on. ...等等。

So now I know that my algorithm for finding connected components is actually wrong. 所以现在我知道我寻找连接组件的算法实际上是错误的。 I guess I need to do some research on that. 我想我需要对此做一些研究。 My first thought is to take the following approach: 我的第一个想法是采用以下方法:

  • Create a copy of the list of connections. 创建连接列表的副本。
  • When we look at the first pair, we don't have any components yet, so add the nodes in the first connection to a new (first) component and remove that connection from the copy. 当我们看第一对时,我们还没有任何组件,因此将第一个连接中的节点添加到新的(第一个)组件中,然后从副本中删除该连接。
  • Look at each successive connection. 查看每个连续的连接。 If one of the nodes in the connection is in the first component: 如果连接中的一个节点在第一个组件中:
    • Add its nodes to the component 将其节点添加到组件
    • Remove that connection from the copy 从副本中删除该连接
    • Go back to the last connection with nodes that we added to the component, and check each connection between that connection and the current connection to see if they connect to the component now. 返回带有我们添加到组件的节点的最后一个连接,并检查该连接和当前连接之间的每个连接,以查看它们是否现在连接到该组件。 If they do, add them to the component and remove them from the copy. 如果有,请将它们添加到组件中,然后从副本中将其删除。 Do this recursively. 递归执行此操作。
  • When we get to the end, repeat the process starting with the (now reduced) copy instead of the list of all connections. 当我们结束时,请从(现已减少的)副本而不是所有连接的列表开始重复该过程。

Any thoughts on that approach would be appreciated. 任何对该方法的想法将不胜感激。

Per comments on the OP above -- 根据以上操作说明-

For anyone interested, I completely redesigned the algorithm (which essentially maps a set of unique two-way edges to a set of connected components [size > 2]). 对于有兴趣的人,我完全重新设计了算法(该算法实际上将一组唯一的双向边缘映射到一组连接的组件[size> 2])。 You can find it in the github repo at https://github.com/IceCreamYou/ViralSpread/blob/master/Statistics.java 您可以在https://github.com/IceCreamYou/ViralSpread/blob/master/Statistics.java的github存储库中找到它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM