[英]Number of simple connected graphs with N labeled vertices and K unlabeled edges
tl;dr My recurrence relation is accounting for a smaller number of graphs than should be. tl; dr我的递归关系占图的数量少于应有的数量。
I need to find the number of simple connected graphs with N labeled vertices and K unlabeled edges. 我需要找到带有N个标记顶点和K个未标记边的简单连接图的数量。 Link to full source with complete question
链接到完整的源,完整的问题
[I have seen this post and it didn't solve my question] [我看过这篇文章 ,但并没有解决我的问题]
Constraints: 2 <= N <= 20. It follows that, N-1 <= K <= N(N-1)/2. 约束:2 <= N <=20。因此,N-1 <= K <= N(N-1)/ 2。
I approached this problem with two different (not quite, I later realized) ideas. 我用两种不同的想法解决了这个问题(后来我才意识到)。
The first idea: Connect N nodes with K edges such that there is 1 path between 2 nodes
Ideation: Consider N-1
nodes and K-1
edges. 第一个想法:
Connect N nodes with K edges such that there is 1 path between 2 nodes
注意:考虑N-1
节点和K-1
边缘。 How many ways to add Nth node? 第N种节点有几种添加方式?
N
and any of the other N-1
nodes; N
和其他N-1
节点之间分配1个边; this is trivial, \\binom {N-1}1, ie, given N-1
choose 1. N-1
选择1。 N-1
edges between .... N-1
边。 The 'formula' I came up with looked something like this: 我想出的“公式”看起来像这样:
We only look at values of K ∈ [N-1, N(N-1)/2] (other values don't make sense). 我们只看K∈[N-1,N(N-1)/ 2]的值(其他值没有意义)。 When K = N-1, it's essentially falls under Cayley's formula .
当K = N-1时,它基本上属于Cayley公式 。 The recurrence relation is the part I came up with.
递归关系是我想到的部分。 The issue is that I am accounting for a smaller number of graphs than should be .
问题是,我所考虑的图形数量少于应有的数量 。 The code:
编码:
static Map<List<Integer>, String> resultMap = new HashMap<List<Integer>, String>();
// N -> number of nodes
// K -> number of edges
// N will be at least 2 and at most 20.
// K will be at least one less than n and at most (n * (n - 1)) / 2
public static String answer(int N, int K) {
/* for the case where K < N-1 */
if(K < N-1)
return BigInteger.ZERO.toString();
/* for the case where K = N-1 */
// Cayley's formula applies [https://en.wikipedia.org/wiki/Cayley's_formula].
// number of trees on n labeled vertices is n^{n-2}.
if(K == N-1)
return BigInteger.valueOf((long)Math.pow(N, N-2)).toString();
/* for the case where K > N-1 */
// check if key is present in the map
List<Integer> tuple = Arrays.asList(N, K);
if( resultMap.containsKey(tuple) )
return resultMap.get(tuple);
// maximum number of edges in a simply
// connected undirected unweighted graph
// with n nodes = |N| * |N-1| / 2
int maxEdges = N * (N-1) / 2;
/* for the case where K = N(N-1)/2 */
// if K is the maximum possible
// number of edges for the number of
// nodes, then there is only one way is
// to make a graph (connect each node
// to all other nodes)
if(K == maxEdges)
return BigInteger.ONE.toString();
/* for the case where K > N(N-1)/2 */
if(K > maxEdges)
return BigInteger.ZERO.toString();
BigInteger count = BigInteger.ZERO;
for(int k = 1; k <= N-1 ; k++) {
BigInteger combinations = nChooseR(N-1, k);
combinations = combinations.multiply(new BigInteger(answer(N-1, K-k)));
count = count.add(combinations);
}
// unmodifiable so key cannot change hash code
resultMap.put(Collections.unmodifiableList(Arrays.asList(N, K)), count.toString());
return count.toString();
}
I found this post on MSE that addresses the same problem. 我发现这对MSE后,解决了同样的问题。 Using that as reference, the 'formula' looked somewhat like this:
以此为参考,“公式”看起来像这样:
This works exactly as expected.
这完全符合预期。 The code for this section is below.
该部分的代码如下。
static Map<List<Integer>, String> resultMap2 = new HashMap<List<Integer>, String>();
// reference: https://math.stackexchange.com/questions/689526/how-many-connected-graphs-over-v-vertices-and-e-edges
public static String answer2(int N, int K) {
/* for the case where K < N-1 */
if(K < N-1)
return BigInteger.ZERO.toString();
/* for the case where K = N-1 */
// Cayley's formula applies [https://en.wikipedia.org/wiki/Cayley's_formula].
// number of trees on n labeled vertices is n^{n-2}.
if(K == N-1)
return BigInteger.valueOf((long)Math.pow(N, N-2)).toString();
/* for the case where K > N-1 */
// check if key is present in the map
List<Integer> tuple = Arrays.asList(N, K);
if( resultMap2.containsKey(tuple) )
return resultMap2.get(tuple);
// maximum number of edges in a simply
// connected undirected unweighted graph
// with n nodes = |N| * |N-1| / 2
int maxEdges = N * (N-1) / 2;
/* for the case where K = N(N-1)/2 */
// if K is the maximum possible
// number of edges for the number of
// nodes, then there is only one way is
// to make a graph (connect each node
// to all other nodes)
if(K == maxEdges)
return BigInteger.ONE.toString();
/* for the case where K > N(N-1)/2 */
if(K > maxEdges)
return BigInteger.ZERO.toString();
// get the universal set
BigInteger allPossible = nChooseR(maxEdges, K);
BigInteger repeats = BigInteger.ZERO;
// now, to remove duplicates, or incomplete graphs
// when can these cases occur?
for(int n = 0 ; n <= N-2 ; n++) {
BigInteger choose_n_from_rem_nodes = nChooseR(N-1, n);
int chooseN = (N - 1 - n) * (N - 2 - n) / 2;
BigInteger repeatedEdges = BigInteger.ZERO;
for(int k = 0 ; k <= K ; k++) {
BigInteger combinations = nChooseR(chooseN, k);
BigInteger recurse = new BigInteger(answer2(n+1, K-k));
repeatedEdges = repeatedEdges.add(combinations.multiply(recurse));
}
repeats = repeats.add(choose_n_from_rem_nodes.multiply(repeatedEdges));
}
// remove repeats
allPossible = allPossible.subtract(repeats);
// add to cache
resultMap2.put(Collections.unmodifiableList(Arrays.asList(N, K)), allPossible.toString());
return resultMap2.get(tuple);
}
I would be grateful if someone could point me in a direction so that I can get the error in my first approach. 如果有人能指出我的方向,以便我能从第一种方法中得到错误,我将不胜感激。 The second approach works, but it makes O(NK) recursive calls and K is on average quadratic in N. So, clearly not very good, although I have tried to minimize computations using DP.
第二种方法可行,但是它进行O(NK)递归调用,并且K在N中平均为二次方。因此,尽管我试图使用DP最小化计算,但是显然不是很好。 The nChooseR() and factorial() functions are below.
下面是nChooseR()和factorial()函数。
Code for nChooseR: nChooseR的代码:
static Map<List<Integer>, BigInteger> nCrMap = new HashMap<List<Integer>, BigInteger>();
// formula: nCr = n! / [r! * (n-r)!]
private static BigInteger nChooseR(int n, int r) {
// check if key is present
List<Integer> tuple = Arrays.asList(n, r);
if( nCrMap.containsKey(tuple) )
return nCrMap.get(tuple);
// covering some basic cases using
// if statements to prevent unnecessary
// calculations and memory wastage
// given 5 objects, there are 0 ways to choose 6
if(r > n)
return BigInteger.valueOf(0);
// given 5 objects, there are 5 ways of choosing 1
// given 5 objects, there are 5 ways of choosing 4
if( (r == 1) || ( (n-r) == 1 ) )
return BigInteger.valueOf(n);
// given 5 objects, there is 1 way of choosing 5 objects
// given 5 objects, there is 1 way of choosing 0 objects
if( (r == 0) || ( (n-r) == 0 ) )
return BigInteger.valueOf(1);
BigInteger diff = getFactorial(n-r);
BigInteger numerator = getFactorial(n);
BigInteger denominator = getFactorial(r);
denominator = denominator.multiply(diff);
// unmodifiable so key cannot change hash code
nCrMap.put(Collections.unmodifiableList(Arrays.asList(n, r)), numerator.divide(denominator));
return nCrMap.get(tuple);
}
Code for factorial: 阶乘代码:
private static Map<Integer, BigInteger> factorials = new HashMap<Integer, BigInteger>();
private static BigInteger getFactorial(int n) {
if(factorials.containsKey(n))
return factorials.get(n);
BigInteger fact = BigInteger.ONE;
for(int i = 2 ; i <= n ; i++)
fact = fact.multiply(BigInteger.valueOf(i));
factorials.put(n, fact);
return fact;
}
Some test code: 一些测试代码:
public static void main(String[] args) {
int fail = 0;
int total = 0;
for(int n = 2 ; n <= 20 ; n++) {
for(int k = n-1 ; k <= n*(n-1)/2 ; k++) {
total++;
String ans = answer(n,k);
String ans2 = answer2(n,k);
if(ans.compareTo(ans2) != 0) {
fail++;
System.out.println("N = " + n + " , K = " + k + " , num = " + ans + " ||| " + ans2);
}
}
}
System.out.println("Approach 1 fails " + ((100*fail)/total) + "% of the test");
}
PS I got this challenge as a part of the Google Foobar challenges. PS:我在Google Foobar挑战中遇到了这个挑战。 Just wanted to make that aware to all.
只是想让所有人都知道这一点。
answer2()
was judged to be working based on the test-cases on Foobar that cannot be seen by the challenge-taker. 根据挑战者无法看到的Foobar的测试用例,判断
answer2()
可以正常工作。 And just for reading all that, here is a video of a tiny hamster eating a tiny burrito . 只是为了阅读所有内容,这是一个小仓鼠吃着小卷饼的视频 。
An alternate approach... 另一种方法...
We know that f(n,n-1) = n^{n-2}
is the counting function of the number of labeled rooted trees [Cayley's formula] 我们知道
f(n,n-1) = n^{n-2}
是标记的有根树的数量的计数函数[Cayley公式]
Now, let f(n, k)
be the total number of connected graphs with n nodes and k edges, we have a characterization of how to add a new edge: 现在,令
f(n, k)
为具有n个节点和k条边的连接图的总数,我们具有如何添加新边的特征:
1) Take any graph in F[n,k], and you can add an edge between any of the {n \\choose 2} - k pairs of unmatched nodes.
1)拍摄F [n,k]中的任何图,然后可以在{n \\ choose 2}-k对不匹配节点之间添加一条边。
2) If you have two connected graphs g_1 and g_2, say in F[s, t] and F[ns, kt] respectively (that is, a connected graph with s nodes and t edges and a connected graph with ns nodes and kt edges), then you can surgically construct a new graph by connecting these two subgraphs together.
2)如果您有两个连通图g_1和g_2,则分别用F [s,t]和F [ns,kt]表示(即,具有s个节点和t个边的连通图,以及具有ns个节点和kt的连通图边缘),则可以通过将这两个子图连接在一起,以外科方式构造一个新图。
You have s * (ns)
pairs of vertices to choose from, and you can choose the s point
in {n \\choose s}
ways. 您有
s * (ns)
对顶点可供选择,并且可以用{n \\choose s}
方式选择s point
。 You can then sum over the choice of s
and t
respectively from 1 to n-1
, and in doing so, you will have double-counted each graph twice. 然后,您可以对
s
和t
分别从1 to n-1
进行选择求和,这样做时,您将对每个图进行两次重复计数。 Let's call this construction g(n, k)
. 我们将此构造称为
g(n, k)
。
Then g(n,k) = (\\sum_s,t {n \\choose s} s (ns) f(s,t) f(ns, kt))/2
然后
g(n,k) = (\\sum_s,t {n \\choose s} s (ns) f(s,t) f(ns, kt))/2
Now, there are no other ways to add in an extra edge (without reducing to the two constructions above), so the additive term h(n,k+1) = (N - k)f(n,k) + g(n,k)
gives a characterization of the multiset of graphs that we've constructed. 现在,没有其他方法可以添加额外的边(不减少上面的两种结构),因此加性项
h(n,k+1) = (N - k)f(n,k) + g(n,k)
表征了我们构建的图的多集合。 Why is this a multiset? 为什么这是多集?
Well, let's look at a case analysis on the two subcases (induction on the construction). 好吧,让我们看一下两个子案例的案例分析(构造的归纳)。 Take a random graph
g
in h(n, k+1)
graphs constructed this way. 在以此方式构造的
h(n, k+1)
图中取一个随机图g
。 The induction hypothesis is that there are k + 1
copies of g within the multiset h(n, k+1)
. 归纳假设是在多集
h(n, k+1)
内有k + 1
g的副本。
Let's just look at the inductive case If you break an edge within a connected graph, then it either remains a connected graph or it breaks into two connected graphs. 让我们看一下归纳情况:如果在连接图中断开一条边,则它要么保持连接图,要么分成两个连接图。 Now, fixate on an
edge e
, if you break any other edges, then e will still be within (k+1) - 1
distinct constructions. 现在,固定在
edge e
,如果折断其他任何边,则e仍将位于(k+1) - 1
不同的构造内。 If you break e
, you will have yet another unique construction. 如果破坏
e
,则将具有另一种独特的构造。 This means that there are k + 1
possible distinct classes of graphs (either single component of two components) from which we can construct the same final graph g
. 这意味着存在
k + 1
可能的不同图类(两个组件中的单个组件),我们可以从中构造相同的最终图g
。
Therefore, h(n,k+1)
counts each graph a total of k+1
times, and so f(n, k+1)
= h(n, k+1)/(k+1)
= ((Nk)f(n,k) + g(n,k))/(k+1)
. 因此,
h(n,k+1)
总共对每个图计数k+1
次,因此f(n, k+1)
= h(n, k+1)/(k+1)
= ((Nk)f(n,k) + g(n,k))/(k+1)
。
Given a fixed n
and k
, this recurrence will compute the correct result in O((nk)^2)
time, so complexity wise, it's equivalent to the previous algorithm. 给定一个固定的
n
和k
,这种重复将在O((nk)^2)
时间内计算出正确的结果,因此从复杂度来看,它等效于先前的算法。 The nice thing about this construction is that it easily yields an analytic generating function so you can do analysis on it. 这种构造的好处是,它很容易产生解析生成函数,因此您可以对其进行分析。
In this case, suppose you have a complex-valued function f_k(x,y)
, then 在这种情况下,假设您有一个复数值函数
f_k(x,y)
,然后
2 dy f_{k+1} = (x^2 dx^2 f_k - 2 y dy f_k) + \\sum_s z^2 dz f_s dz f_{ks}
. 2 dy f_{k+1} = (x^2 dx^2 f_k - 2 y dy f_k) + \\sum_s z^2 dz f_s dz f_{ks}
。
You'll need a lot of complex analysis machinery to solve this recurrence PDE. 您将需要很多复杂的分析机制来解决此重复PDE。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.