-<source srcset=https://matplotlib.org/matplotblog/posts/draw-all-graphs-of-n-nodes/thumbnail_hu8858ad23dbc7c9b2648b849bc08f52cf_293428_800x0_resize_lanczos_2.png><img src=https://matplotlib.org/matplotblog/posts/draw-all-graphs-of-n-nodes/thumbnail_hu8858ad23dbc7c9b2648b849bc08f52cf_293428_800x0_resize_lanczos_2.png></picture><p>The other day I was homeschooling my kids, and they asked me: “Daddy, can you draw us all possible non-isomorphic graphs of 3 nodes”? Or maybe I asked them that? Either way, we happily draw all possible graphs of 3 nodes, but already for 4 nodes it got hard, and for 5 nodes - <a href=https://www.graphclasses.org/smallgraphs.html#nodes5>plain impossible</a>!</p><p>So I thought: let me try to write a brute-force program to do it! I spent a few hours sketching some smart dynamic programming solution to generate these graphs, and went nowhere, as apparently the <a href=http://www.cs.columbia.edu/~cs4205/files/CM9.pdf>problem is quite hard</a>. I gave up, and decided to go with a naive approach:</p><ol><li>Generate all graphs of N nodes, even if some of them look the same (are isomorphic). For \(N\) nodes, there are \(\frac{N(N-1)}{2}\) potential edges to connect these nodes, so it's like generating a bunch of binary numbers. Simple!</li><li>Write a program to tell if two graphs are isomorphic, then remove all duplicates, unworthy of being presented in the final picture.</li></ol><p>This strategy seemed more reasonable, but writing a “graph-comparator” still felt like a cumbersome task, and more importantly, this part would itself be slow, as I'd still have to go through a whole tree of options for every graph comparison. So after some more head-scratching, I decided to simplify it even further, and use the fact that these days the memory is cheap:</p><ol><li>Generate all possible graphs (some of them totally isomorphic, meaning that they would look as a repetition if plotted on a figure)</li><li>For each graph, generate its “description” (like an <a href=https://en.wikipedia.org/wiki/Adjacency_matrix>adjacency matrix</a>, or an edge list), and check if a graph with this description is already on the list. If yes, skip it, we got its portrait already!</li><li>If however the graph is unique, include it in the picture, and also generate all possible “descriptions” of it, up to node permutation, and add them to the hash table. To make sure no other graph of this particular shape would ever be included in our pretty picture again.</li></ol><p>For the first task, I went with the edge list, which made the task identical to <a href=https://www.geeksforgeeks.org/generate-all-the-binary-strings-of-n-bits/>generating all binary numbers</a> of length \(\frac{N(N-1)}{2}\) with a recursive function, except instead of writing zeroes you skip edges, and instead of writing ones, you include them. Below is the function that does the trick, and has an additional bonus of listing all edges in a neat orderly way. For every edge \(i \rightarrow j\) we can be sure that \(i\) is lower than \(j\), and also that edges are sorted as words in a dictionary. Which is good, as it restricts the set of possible descriptions a bit, which will simplify our life later.</p><div class=highlight><pre style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=color:#66d9ef>def</span> <span style=color:#a6e22e>make_graphs</span>(n<span style=color:#f92672>=</span><span style=color:#ae81ff>2</span>, i<span style=color:#f92672>=</span>None, j<span style=color:#f92672>=</span>None):
0 commit comments