排列組合是計算應用經常使用的算法,通常使用遞歸的方式計算,但是由於n!的過於大,暴力計算很不明智。一般使用以下兩種方式計算。
一,遞歸的思想:假設m中取n個數計算排列組合數,表示為comb(m,n)。那么comb(m,n)= comb(m-1,n-1)+comb(m-1,n)
解釋思想,從m個球中取出n個球可以分成兩種情況相加,從m個球中取出一個球,如果它屬於n,還需要從m-1中取出n-1個球;如果它不屬於n,則需要從m-1中取出n個球
根據這種思想可以通過遞歸的思想計算組合數:
private static long comb(int m,int n){if(n==0) return 1; if (n==1) return m; if(n>m/2) return comb(m,m-n); if(n>1) return comb(m-1,n-1)+comb(m-1,n);
return -1; //通過編譯需要,數字無實際意義
}
適用遞歸計算,當數字較大時,遞歸深度過深,會相對耗時,甚至堆棧溢出。如果對性能有要求,可以建立鍵-值對,存儲計算結果,防止,反復計算。
static Map<String,Long> map= new HashMap<String, Long>(); private static long comb(int m,int n){ String key= m+","+n; if(n==0) return 1; if (n==1) return m; if(n>m/2) return comb(m,m-n); if(n>1){ if(!map.containsKey(key)) map.put(key, comb(m-1,n-1)+comb(m-1,n)); return map.get(key); } return -1; }
二,對數的計算思想:跟據定義,comb(m,n)=m!/(m-n)!n!
兩邊取對數,log(comb(m,n))=log(m!/n!)-log((m-n)!)
計算之后,再通過exp計算最終結果。優點,不會出現數組越界,缺點:計算非常大的數字時,由於精度誤差,結果轉化為整數時可能有偏差
private static double comb_log(int m,int n){ int i; if(n>m-n) n=m-n; double s1=0.0; double s2=0.0; for (int j = m-n+1; j <=m; j++) { s1+=Math.log(j); } for (int j = 1; j <=n; j++) { s2+=Math.log(j); } return Math.exp(s1-s2); }