【问题标题】:Number of simple connected graphs with N labeled vertices and K unlabeled edges具有 N 个标记顶点和 K 个未标记边的简单连通图的数量
【发布时间】:2016-06-15 01:48:50
【问题描述】:

tl;博士 我的循环关系占用的图表数量少于应有的数量。

我需要找到具有 N 个标记顶点和 K 个未标记边的简单连通图的数量。 Link to full source with complete question

[我见过this post,它没有解决我的问题]

约束: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。
  • 在...之间分配 2 条边。
  • ....
  • ....
  • 在 .... 之间分配 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 帖子解决了同样的问题。使用它作为参考,“公式”看起来有点像这样: 这完全符合预期。本节的代码如下。

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() 函数如下。

nChoosR 的代码:

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");
}

附注作为 Google Foobar 挑战的一部分,我得到了这个挑战。只是想让所有人都知道这一点。 answer2() 根据挑战者看不到的 Foobar 上的测试用例被判定为有效。 只是为了阅读所有内容,这里是video of a tiny hamster eating a tiny burrito

【问题讨论】:

  • N 和 K 的约束条件是什么?
  • 抱歉没有提及。 2
  • 您能否为问题添加更多详细信息?我仍然无法得到什么要求?你有 N 个节点和 K 条边,找到从这些节点和边中形成一个
  • 是的。你是对的。

标签: java algorithm graph hashmap dynamic-programming


【解决方案1】:

另一种方法...

我们知道f(n,n-1) = n^{n-2}是个数的计数函数 标记有根树[Cayley 公式]

现在,设f(n, k) 为具有 n 个节点和 k 个边的连通图的总数, 我们已经描述了如何添加新边:

1) 取 F[n,k] 中的任意图,您可以在任意图之间添加一条边 {n \choose 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} 方式。然后,您可以总结选择 st 分别来自 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) 给出了我们已经得到的多组图的特征 建。为什么这是一个多重集?

好吧,我们来看两个子案例的案例分析 (施工感应)。在h(n, k+1) 图中取随机图g 以这种方式构建。归纳假设是 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).

给定一个固定的nk,这个循环将在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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-05
    • 2016-12-12
    相关资源
    最近更新 更多