TL;博士 我的递归关系占图表的数量应该比应该少。
我需要找到具有N个标记顶点和K个未标记边缘的简单连通图的数量。 Link to full source with complete question
[我见过this post并且它没有解决我的问题]
约束:2 <= N <= 20.由此得出,N-1 <= K <= N(N-1)/ 2.
我用两种不同的(不是我后来意识到的)想法解决了这个问题。
第一个想法:Connect N nodes with K edges such that there is 1 path between 2 nodes
构思:考虑N-1
个节点和K-1
个边缘。添加第N个节点的方法有多少?
N
和任何其他N-1
节点之间分配1条边;
这是微不足道的,\ binom {N-1} 1,即给定N-1
选择1。N-1
个边缘。我们只看K∈[N-1,N(N-1)/ 2]的值(其他值没有意义)。当K = N-1时,它基本上落在Cayley's formula之下。递归关系是我想出的部分。 问题在于我占的图表数量应该少于。代码:
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();
}
我在MSE上发现了this帖子,解决了同样的问题。使用它作为参考,&#39;公式&#39;看起来有点像这样: 这完全符合预期。本节的代码如下。
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);
}
如果有人能指出我的方向以便我能在第一种方法中得到错误,我将不胜感激。第二种方法有效,但它使O(NK)递归调用和K在N中平均为二次方。所以,显然不是很好,尽管我试图使用DP最小化计算。 nChooseR()和factorial()函数如下。
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);
}
阶乘代码:
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;
}
一些测试代码:
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");
}
P.S。作为Google Foobar挑战的一部分,我接受了这一挑战。只是想让所有人意识到这一点。根据挑战者无法看到的Foobar测试用例判断answer2()
正在工作。
只是为了阅读所有内容,这里是video of a tiny hamster eating a tiny burrito。
答案 0 :(得分:0)
另一种方法......
我们知道f(n,n-1) = n^{n-2}
是数量的计数函数
标记的有根树[Cayley的公式]
现在,让f(n, k)
为具有n个节点和k个边的连通图的总数,
我们有一个关于如何添加新边缘的特征:
1)取F [n,k]中的任何图形,你可以在任何一个之间添加一条边 {n \选择2} - k对不匹配的节点。
2)如果您有两个连接图g_1和g_2,请说F [s,t]和 分别为F [n-s,k-t](即,具有s个节点的连通图 和t边和带有n-s节点和k-t边的连通图), 那么你可以通过连接这些来手术构建一个新的图形 两个子图一起。
您有s * (n-s)
对顶点可供选择,您可以选择
以s point
方式{n \choose s}
。然后你可以总结选择
来自s
的{{1}}和t
分别来自1 to n-1
每个图表重复两次。让我们称之为g(n, k)
。
然后g(n,k) = (\sum_s,t {n \choose s} s (n-s) f(s,t) f(n-s, k-t))/2
现在,没有其他方法可以添加额外的边缘(不减少到
上面的两个结构),所以添加词
h(n,k+1) = (N - k)f(n,k) + g(n,k)
给出了我们所描述的多重图表的特征
建造。为什么这是一个多重集?
那么,让我们看一下两个子类的案例分析
(对施工的归纳)。在g
图表中随机抽取h(n, k+1)
图表
以这种方式构建。归纳假设是存在的
多个集k + 1
中的h(n, k+1)
个g副本。
让我们看一下归纳案例
如果在连接图中打破边缘,则它仍然是连接的
图形或它分成两个连接的图形。
现在,注意edge e
,如果你打破任何其他边缘,那么e仍将是
在(k+1) - 1
个不同的结构中。如果你打破e
,你就会有
另一种独特的建筑
这意味着有k + 1
个可能的不同类图
(我们可以构建相同的两个组件的单个组件)
最终图g
。
因此,h(n,k+1)
计算每个图表的总计k+1
次,等等
f(n, k+1)
= h(n, k+1)/(k+1)
= ((N-k)f(n,k) + g(n,k))/(k+1)
。
给定固定的n
和k
,此重复将在O((nk)^2)
时间内计算出正确的结果,
如此复杂,它与以前的算法相当。
这种结构的好处在于它很容易产生分析
生成功能,以便您可以对其进行分析
在这种情况下,假设您有一个复值函数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_{k-s}
。
你需要很多复杂的分析机制来解决这种复发PDE。
这是一个java实现[source]