[英]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。 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
。 然后,您可以對s
和t
分別從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)
。
給定一個固定的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_{ks}
。
您將需要很多復雜的分析機制來解決此重復PDE。
這是一個Java實現[ 來源 ]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.