簡體   English   中英

具有N個標記頂點和K個未標記邊的簡單連接圖的數量

[英]Number of simple connected graphs with N labeled vertices and K unlabeled edges

tl; dr我的遞歸關系占圖的數量少於應有的數量。

我需要找到帶有N個標記頂點和K個未標記邊的簡單連接圖的數量。 鏈接到完整的源,完整的問題

[我看過這篇文章 ,但並沒有解決我的問題]

約束: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。
  • 在...之間分配2條邊。
  • ....
  • ....
  • 在...之間分布N-1邊。

我想出的“公式”看起來像這樣: 第一種方法

我們只看K∈[N-1,N(N-1)/ 2]的值(其他值沒有意義)。 當K = N-1時,它基本上屬於Cayley公式 遞歸關系是我想到的部分。 問題是,我所考慮的圖形數量少於應有的數量 編碼:

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后,解決了同樣的問題。 以此為參考,“公式”看起來像這樣: 第二種方法 這完全符合預期。 該部分的代碼如下。

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

PS:我在Google Foobar挑戰中遇到了這個挑戰。 只是想讓所有人都知道這一點。 根據挑戰者無法看到的Foobar的測試用例,判斷answer2()可以正常工作。 只是為了閱讀所有內容,這是一個小倉鼠吃着小卷餅視頻

另一種方法...

我們知道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 [ns,kt]表示(即,具有s個節點和t個邊的連通圖,以及具有ns個節點和kt的連通圖邊緣),則可以通過將這兩個子圖連接在一起,以外科方式構造一個新圖。

您有s * (ns)對頂點可供選擇,並且可以用{n \\choose s}方式選擇s point 然后,您可以對st分別從1 to n-1進行選擇求和,這樣做時,您將對每個圖進行兩次重復計數。 我們將此構造稱為g(n, k)

然后g(n,k) = (\\sum_s,t {n \\choose s} s (ns) f(s,t) f(ns, kt))/2

現在,沒有其他方法可以添加額外的邊(不減少上面的兩種結構),因此加性項h(n,k+1) = (N - k)f(n,k) + g(n,k)表征了我們構建的圖的多集合。 為什么這是多集?

好吧,讓我們看一下兩個子案例的案例分析(構造的歸納)。 在以此方式構造的h(n, k+1)圖中取一個隨機圖g 歸納假設是在多集h(n, k+1)內有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) = ((Nk)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_{ks}

您將需要很多復雜的分析機制來解決此重復PDE。

這是一個Java實現[ 來源 ]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM