hil*_*lem 7 python algorithm performance graph-theory networkx
我正在尝试生成具有给定数量节点的所有有向图,直至图同构,以便我可以将它们输入到另一个 Python 程序中。这是一个使用 NetworkX 的简单参考实现,我想加快速度:
from itertools import combinations, product
import networkx as nx
def generate_digraphs(n):
graphs_so_far = list()
nodes = list(range(n))
possible_edges = [(i, j) for i, j in product(nodes, nodes) if i != j]
for edge_mask in product([True, False], repeat=len(possible_edges)):
edges = [edge for include, edge in zip(edge_mask, possible_edges) if include]
g = nx.DiGraph()
g.add_nodes_from(nodes)
g.add_edges_from(edges)
if not any(nx.is_isomorphic(g_before, g) for g_before in graphs_so_far):
graphs_so_far.append(g)
return graphs_so_far
assert len(generate_digraphs(1)) == 1
assert len(generate_digraphs(2)) == 3
assert len(generate_digraphs(3)) == 16
Run Code Online (Sandbox Code Playgroud)
此类图的数量似乎增长得相当快,并且由该OEIS 序列给出。我正在寻找一种解决方案,能够在合理的时间内生成最多 7 个节点的所有图表(总共约 10 亿张图表)。
将图表示为 NetworkX 对象并不是很重要;例如,用邻接列表表示图形或使用不同的库对我来说很好。
\xe2\x80\x99s 是我从 Brendan McKay\xe2\x80\x99s 论文\n\xe2\x80\x9c无同构详尽生成 \xe2\x80\x9d 中学到的一个有用的想法\xe2\x80\x9d(尽管我相信它早于\n纸)。
\n这个想法是,我们可以将同构类组织成一棵树,\n其中具有空图的单例类是根,并且每个\n具有 n > 0 个节点的图的类都有一个父类,其图\n具有 n \xe2\ x88\x92 1 个节点。要枚举具有\n > 0 个节点的图的同构类,请枚举具有 n \xe2\x88\x92 1\n 个节点的图的同构类,并且对于每个这样的类,以所有\n可能的方式将其代表扩展到 n 个节点,并且过滤掉实际上\xe2\x80\x99t\n子项。
\n下面的 Python 代码通过一个基本但不平凡的图同构子例程实现了这个想法。对于 n =\n6 需要几分钟,对于 n = 7(此处估计)大约需要几天。为了获得额外\n速度,请将其移植到 C++,也许会找到更好的算法来处理\n排列组(也许在TAoCP,虽然大多数图都没有\n对称性,所以\xe2\x80\x99s不清楚好处有多大)。
\nimport cProfile\nimport collections\nimport itertools\nimport random\n\n\n# Returns labels approximating the orbits of graph. Two nodes in the same orbit\n# have the same label, but two nodes in different orbits don't necessarily have\n# different labels.\ndef invariant_labels(graph, n):\n labels = [1] * n\n for r in range(2):\n incoming = [0] * n\n outgoing = [0] * n\n for i, j in graph:\n incoming[j] += labels[i]\n outgoing[i] += labels[j]\n for i in range(n):\n labels[i] = hash((incoming[i], outgoing[i]))\n return labels\n\n\n# Returns the inverse of perm.\ndef inverse_permutation(perm):\n n = len(perm)\n inverse = [None] * n\n for i in range(n):\n inverse[perm[i]] = i\n return inverse\n\n\n# Returns the permutation that sorts by label.\ndef label_sorting_permutation(labels):\n n = len(labels)\n return inverse_permutation(sorted(range(n), key=lambda i: labels[i]))\n\n\n# Returns the graph where node i becomes perm[i] .\ndef permuted_graph(perm, graph):\n perm_graph = [(perm[i], perm[j]) for (i, j) in graph]\n perm_graph.sort()\n return perm_graph\n\n\n# Yields each permutation generated by swaps of two consecutive nodes with the\n# same label.\ndef label_stabilizer(labels):\n n = len(labels)\n factors = (\n itertools.permutations(block)\n for (_, block) in itertools.groupby(range(n), key=lambda i: labels[i])\n )\n for subperms in itertools.product(*factors):\n yield [i for subperm in subperms for i in subperm]\n\n\n# Returns the canonical labeled graph isomorphic to graph.\ndef canonical_graph(graph, n):\n labels = invariant_labels(graph, n)\n sorting_perm = label_sorting_permutation(labels)\n graph = permuted_graph(sorting_perm, graph)\n labels.sort()\n return max(\n (permuted_graph(perm, graph), perm[sorting_perm[n - 1]])\n for perm in label_stabilizer(labels)\n )\n\n\n# Returns the list of permutations that stabilize graph.\ndef graph_stabilizer(graph, n):\n return [\n perm\n for perm in label_stabilizer(invariant_labels(graph, n))\n if permuted_graph(perm, graph) == graph\n ]\n\n\n# Yields the subsets of range(n) .\ndef power_set(n):\n for r in range(n + 1):\n for s in itertools.combinations(range(n), r):\n yield list(s)\n\n\n# Returns the set where i becomes perm[i] .\ndef permuted_set(perm, s):\n perm_s = [perm[i] for i in s]\n perm_s.sort()\n return perm_s\n\n\n# If s is canonical, returns the list of permutations in group that stabilize s.\n# Otherwise, returns None.\ndef set_stabilizer(s, group):\n stabilizer = []\n for perm in group:\n perm_s = permuted_set(perm, s)\n if perm_s < s:\n return None\n if perm_s == s:\n stabilizer.append(perm)\n return stabilizer\n\n\n# Yields one representative of each isomorphism class.\ndef enumerate_graphs(n):\n assert 0 <= n\n if 0 == n:\n yield []\n return\n for subgraph in enumerate_graphs(n - 1):\n sub_stab = graph_stabilizer(subgraph, n - 1)\n for incoming in power_set(n - 1):\n in_stab = set_stabilizer(incoming, sub_stab)\n if not in_stab:\n continue\n for outgoing in power_set(n - 1):\n out_stab = set_stabilizer(outgoing, in_stab)\n if not out_stab:\n continue\n graph, i_star = canonical_graph(\n subgraph\n + [(i, n - 1) for i in incoming]\n + [(n - 1, j) for j in outgoing],\n n,\n )\n if i_star == n - 1:\n yield graph\n\n\ndef test():\n print(sum(1 for graph in enumerate_graphs(5)))\n\n\ncProfile.run("test()")\n
Run Code Online (Sandbox Code Playgroud)\n