在图中查找3个节点(或三角形)的循环

时间:2009-11-10 05:33:18

标签: python graph geometry cycle

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

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

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

11 个答案:

答案 0 :(得分:4)

一百万个边缘非常小。除非你做了数千次,否则只需使用一个天真的实现。

我假设你有一个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图库。

答案 1 :(得分:2)

我不想听起来很苛刻,但你有没有试过谷歌呢?第一个链接是一个非常快速的算法: http://www.mail-archive.com/algogeeks@googlegroups.com/msg05642.html

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

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

答案 2 :(得分:2)

非常简单明了的方法是使用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个节点的循环/循环中。

答案 3 :(得分:2)

假设它是一个无向图,答案就在于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将给出仅包含三角形的边列表。

答案 4 :(得分:1)

即使效率不高,您也可能希望实现解决方案,因此请使用循环。写一个测试,这样你就可以知道需要多长时间。

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

拥有更快的算法可能会错过某些东西可能比慢速算法更差。

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

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

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

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

答案 5 :(得分:1)

我正在研究在无向图表上计算三角形数量的相同问题,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 :(得分:0)

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

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

如果你需要找到所有的三角形 - 具体来说,每个节点都连接到另外两个节点的3个节点的所有组 - 那么你需要在很长的'每个'循环'中检查每个可能的组。

唯一的优化是确保您不会两次检查相同的“群组”,例如如果你已经测试了B& C不在A组中,那么不检查是否A& C与B组合在一起。

答案 7 :(得分:0)

很惊讶没有提到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 :(得分:0)

如果你不关心不同顺序的同一个三角形的多个副本,那么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]是一种快速迭代或查找邻居的方式。

如果您想摆脱重新排序,请将每个三元组转换为冻结集并制作一组frozensets:

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

如果你不喜欢冷冻套装,并想要一套套装,那么:

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 :(得分:0)

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

enumerate_all_cliques networkx方法确实会在图表中返回所有派系,无论其长度如何;因此循环可能需要很长时间(特别是对于非常密集的图形)。

此外,一旦为三角形定义,只需要参数化来为每个团队长度推广该方法,所以这是一个函数:

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

中未提供有向图中的派系算法

答案 10 :(得分:0)

我刚刚发现nx.edge_disjoint_paths可以计算包含某些边的三角形。比nx.enumerate_all_cliquesnx.cycle_basis更快。 它返回源和目标之间的边缘不相交路径。边缘不相交路径是不共享任何边缘的路径。
结果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)