排列组合是计算应用经常使用的算法,通常使用递归的方式计算,但是由于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); }