【问题标题】:Finding cycle of 3 nodes ( or triangles) in a graph在图中查找 3 个节点(或三角形)的循环
【发布时间】:2009-11-10 05:33:17
【问题描述】:

我正在处理复杂的网络。我想找到在给定图中形成 3 个节点(或三角形)的循环的节点组。由于我的图表包含大约百万条边,因此使用简单的迭代解决方案(多个“for”循环)效率不高。

我正在使用python进行编程,如果这些是一些用于处理这些问题的内置模块,请告诉我。

如果有人知道任何可用于在图中找到三角形的算法,请回复。

【问题讨论】:

  • 你考虑过哪些算法?你试过什么?

标签: python graph geometry cycle


【解决方案1】:

假设它是一个无向图,答案就在 python 的 networkx 库中。 如果您只需要计算三角形,请使用:

import networkx as nx
tri=nx.triangles(g)

但如果你需要知道具有三角形(三元)关系的边列表,请使用

all_cliques= nx.enumerate_all_cliques(g)

这会给你所有的派系 (k=1,2,3...max degree - 1)

所以,只过滤三角形,即 k=3,

triad_cliques=[x for x in all_cliques if len(x)==3 ]

triad_cliques 将给出一个只有三角形的边列表。

【讨论】:

    【解决方案2】:

    一百万条边非常小。除非您这样做数千次,否则请使用幼稚的实现。

    我假设您有一个 node_ids 字典,它指向它们的邻居序列,并且该图是有向的。

    例如:

    nodes = {}
    nodes[0] = 1,2
    nodes[1] = tuple() # empty tuple
    nodes[2] = 1
    

    我的解决方案:

    def generate_triangles(nodes):
        """Generate triangles. Weed out duplicates."""
        visited_ids = set() # remember the nodes that we have tested already
        for node_a_id in nodes:
            for node_b_id in nodes[node_a_id]:
                if nod_b_id == node_a_id:
                    raise ValueError # nodes shouldn't point to themselves
                if node_b_id in visited_ids:
                    continue # we should have already found b->a->??->b
                for node_c_id in nodes[node_b_id]:
                    if node_c_id in visited_ids:
                        continue # we should have already found c->a->b->c
                    if node_a_id in nodes[node_c_id]:
                        yield(node_a_id, node_b_id, node_c_id)
            visited_ids.add(node_a_id) # don't search a - we already have all those cycles
    

    检查性能:

    from random import randint
    n = 1000000
    node_list = range(n)
    nodes = {}
    for node_id in node_list:
        node = tuple()
        for i in range(randint(0,10)): # add up to 10 neighbors
            try:
                neighbor_id = node_list[node_id+randint(-5,5)] # pick a nearby node
            except:
                continue 
            if not neighbor_id in node:
                node = node + (neighbor_id,)
        nodes[node_id] = node
    
    cycles = list(generate_triangles(nodes))
    print len(cycles)
    

    当我尝试时,构建随机图比计算周期花费的时间更长。

    你可能想测试一下;)我不保证它是正确的。

    你也可以看看networkx,这是一个大的python图形库。

    【讨论】:

      【解决方案3】:

      非常简单明了的方法是使用 Networkx:

      使用 Networkx,您可以通过 nx.cycle_basis(G) 获得无向图的循环,然后选择具有 3 个节点的循环

      cycls_3 = [c for c in nx.cycle_basis(G) if len(c)==3]
      

      或者您可以通过find_cliques(G) 找到所有派系,然后选择您想要的派系(具有 3 个节点)。 cliques 是图中所有节点相互连接的部分,它们发生在具有 3 个节点的循环/循环中。

      【讨论】:

        【解决方案4】:

        我不想听起来刺耳,但你试过用谷歌搜索吗?第一个链接是一个非常快速的算法来做到这一点: http://www.mail-archive.com/algogeeks@googlegroups.com/msg05642.html

        还有一篇关于 ACM 的文章(您可能可以访问): http://portal.acm.org/citation.cfm?id=244866 (如果你没有访问权限,我相信如果你问写它的女士,你会得到一份。)

        另外,我可以想象一个基于clique-decomposition的三角形枚举方法,但我不知道它是否在某个地方描述过。

        【讨论】:

          【解决方案5】:

          我正在解决在无向图形上计算三角形数量的相同问题,wisty 的解决方案在我的情况下非常有效。我对其进行了一些修改,因此只计算无向三角形。

              #### function for counting undirected cycles
              def generate_triangles(nodes):
                  visited_ids = set() # mark visited node
                  for node_a_id in nodes:
                      temp_visited = set() # to get undirected triangles
                      for node_b_id in nodes[node_a_id]:
                          if node_b_id == node_a_id:
                              raise ValueError # to prevent self-loops, if your graph allows self-loops then you don't need this condition
                          if node_b_id in visited_ids:
                              continue
                          for node_c_id in nodes[node_b_id]:
                              if node_c_id in visited_ids:
                                  continue    
                              if node_c_id in temp_visited:
                                  continue
                              if node_a_id in nodes[node_c_id]:
                                  yield(node_a_id, node_b_id, node_c_id)
                              else:
                                  continue
                          temp_visited.add(node_b_id)
                      visited_ids.add(node_a_id)
          

          当然,例如你需要使用字典

              #### Test cycles ####
          
              nodes = {}
          
              nodes[0] = [1, 2, 3]
              nodes[1] = [0, 2]
              nodes[2] = [0, 1, 3]
              nodes[3] = [1]
          
              cycles = list(generate_triangles(nodes))
              print cycles
          

          使用 Wisty 的代码,找到的三角形将是 [(0, 1, 2), (0, 2, 1), (0, 3, 1), (1, 2, 3)]

          将三角形 (0, 1, 2) 和 (0, 2, 1) 视为两个不同的三角形。使用我修改的代码,这些只算作一个三角形。

          我将它与一个相对较小的字典一起使用,该字典不到 100 个键,每个键平均有 50 个值。

          【讨论】:

            【解决方案6】:

            即使效率不高,您也可能希望实施解决方案,因此请使用循环。编写一个测试,以便您了解需要多长时间。

            然后,当您尝试新方法时,您可以做两件事: 1) 确保答案保持不变。 2) 看看有什么改进。

            拥有一个更快的算法会遗漏一些东西可能会比拥有一个更慢的算法更糟糕。

            进行慢速测试后,您可以查看是否可以并行执行此操作,并查看性能提升情况。

            然后,您可以查看是否可以标记所有小于 3 个顶点的节点。

            理想情况下,您可能希望先将其缩小到 100 左右,这样您就可以绘制它,并以图形方式查看发生了什么。

            有时你的大脑会看到一个在查看算法时并不那么明显的模式。

            【讨论】:

              【解决方案7】:

              惊讶地发现没有提及 Networkx 三角形函数。我知道它不一定会返回形成三角形的节点组,但应该与许多在此页面上发现自己的人非常相关。

              nx.triangles(G) # list of how many triangles each node is part of
              sum(nx.triangles(G).values())/3 # total number of triangles
              

              返回节点簇的另一种方法是......

              for u,v,d in G.edges(data=True):
                  u_array = adj_m.getrow(u).nonzero()[1] # get lists of all adjacent nodes
                  v_array = adj_m.getrow(v).nonzero()[1]
                  # find the intersection of the two sets - these are the third node of the triangle
                  np.intersect1d(v_array,u_array)
              

              【讨论】:

                【解决方案8】:

                如果您不关心同一个三角形以不同顺序的多个副本,那么 3 元组列表就可以了:

                from itertools import combinations as combos
                [(n,nbr,nbr2) for n in G for nbr, nbr2 in combos(G[n],2) if nbr in G[nbr2]]
                

                这里的逻辑是检查每个节点的每一对邻居,看它们是否连通。 G[n] 是一种快速迭代或查找邻居的方法。

                如果您想摆脱重新排序,请将每个三元组变成一个frozenset,并制作一组frozenset:

                set(frozenset([n,nbr,nbr2]) for n in G for nbr, nbr2 in combos(G[n]) if nbr in G[nbr2])
                

                如果你不喜欢frozenset并且想要一个集合列表,那么:

                triple_iter = ((n, nbr, nbr2) for n in G for nbr, nbr2 in combos(G[n],2) if nbr in G[nbr2])
                triangles = set(frozenset(tri) for tri in triple_iter)
                nice_triangles = [set(tri) for tri in triangles]
                

                【讨论】:

                  【解决方案9】:

                  您需要找到“所有”的“三角形”,还是只找到“一些”/“任何”? 或者您只需要测试特定节点是否是三角形的一部分?

                  测试很简单 - 给定一个节点 A,是否有任何两个连接的节点 B 和 C 也直接连接。

                  如果您需要找到所有三角形 - 具体来说,每个节点都连接到其他两个节点的所有 3 个节点组 - ​​那么您需要在一个非常长时间运行的“for each”循环中检查每个可能的组。

                  唯一的优化是确保您不会检查同一个“组”两次,例如如果您已经测试过 B & C 不在与 A 的组中,则不要检查 A & C 是否在与 B 的组中。

                  【讨论】:

                    【解决方案10】:

                    这是Ajay M answer 的更高效版本(我会评论它,但我没有足够的声誉)。

                    确实,networkxenumerate_all_cliques 方法将返回图中的所有 个团,无论它们的长度如何;因此循环它可能需要很多时间(尤其是对于非常密集的图形)。

                    此外,一旦为三角形定义,它只是一个参数化的问题来概括每个团长度的方法,所以这里有一个函数:

                    import networkx as nx
                    
                    def get_cliques_by_length(G, length_clique):
                        """ Return the list of all cliques in an undirected graph G with length 
                        equal to length_clique. """
                        cliques = []
                        for c in nx.enumerate_all_cliques(G) :
                            if len(c) <= length_clique:
                                if len(c) == length_clique:
                                    cliques.append(c)            
                            else:
                                return cliques
                        # return empty list if nothing is found
                        return cliques
                    

                    要获得三角形,只需使用get_cliques_by_length(G, 3)

                    警告:此方法仅适用于无向图。 networkx中没有提供有向图中的团组算法

                    【讨论】:

                      【解决方案11】:

                      我刚刚发现nx.edge_disjoint_paths 可以计算包含某些边的三角形。比nx.enumerate_all_cliquesnx.cycle_basis 快。 它返回源和目标之间的边缘不相交路径。边缘不相交路径是不共享任何边缘的路径。
                      而result-1是包含某些边或源节点和目标节点之间的三角形的数量。

                      edge_triangle_dict = {}
                      for i in g.edges:
                          edge_triangle_dict[i] = len(list(nx.edge_disjoint_paths(g, i[0], i[1]))-1)
                      

                      【讨论】:

                        猜你喜欢
                        • 2023-01-15
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2013-12-13
                        • 2012-09-16
                        • 1970-01-01
                        相关资源
                        最近更新 更多