数学中有个经典的组合问题,即一个m元素组成结合C取其中n个元素 (0<n<=m ,n,m为整数),与多少种组合。在读高中时这个问题是数学的一大重点难点,笔者印象深刻。在大学研究算法时也碰到这种问题,当时试着按高中时的解决方案如:挡板法、归类法去遍历所有的组合情况时却发现没那么容易,用函数递归的编程方法,很容易就死循环了。当时学线性代数的矩阵时有种算法叫 “0|1置换法”跟排列组合的概念相符合,就尝试用这种方法来遍历组合问题。后来参加工作了,在某互联网婚恋网站负责一个情缘匹配的需求,里面会根据男女的身材、相貌、工作、收入、家庭、年龄、性格等多维因素做一个匹配,因为数据量大不能实时计算、要事先计算好结果,所以就要先计算出有多少种组合,每种组合人群归类,然后给红娘去定向推广。因为涉及公司业务,此处不举这个例子。今天整理文档,发现了这个算法,直接贴出算法和代码实现,供有兴趣者参考:
求一个组合C(m,n)
算法如下
1、开一个数组a[m],其下标表示1到m个数
2、数组元素的值为1表示其下标代表的元素被选中,为0则没选中。
3、首先初始化,将数组a[0]~a[n-1]个元素置1,a[n]~a[m-1]置0,由此得到第一个组合
4、从左到右扫描,找到a[i-1]=1,a[i]=0的组合 ,找到第一个这种组合后将其值对换,同时将其左边的所有“1”全部移动到数组的最左端
5、当第一个“1”移动到数组的m-n的位置,即n个“1”全部移动到最右端时,就得 到了最后一个组合。
比如组合 [A,B,C,D,E]中5取3的组合情况,按算法步骤如下:
1 1 1 0 0
1 1 0 1 0
1 0 1 1 0
0 1 1 1 0
1 1 0 0 1
1 0 1 0 1
0 1 1 0 1
1 0 0 1 1
0 1 0 1 1
0 0 1 1 1
根据算法第2点 1位选中 0未选中,得到的结果即是:
A B C
A B D
A C D
B C D
A B E
A C E
B C E
A D E
B D E
C D E
代码实现比较粗陋,可以优化:
import java.util.ArrayList; import java.util.List; /** * 组合算法 * * @author zkd * @Date 2014/5/9 */ public class Combination { //返回n中选m的组合序列 1<=m<=n public List<int[]> generateCombination(int n, int m) { List<int[]> list = new ArrayList<>(); int[] tempNum = new int[n]; boolean flag; // 判断算法的结束 // 初始化数组,同时得到第一组组合系列 for (int i = 0; i < n; i++) { if (i < m) { tempNum[i] = 1; } else { tempNum[i] = 0; } } int[] b = tempNum.clone(); list.add(b); if (n == m) { return list; } do { int pose = 0; // 记录改变的位置 int sum = 0; // 记录改变位置 左侧 1 的个数 // 然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为“01” for (int i = 0; i < (n - 1); i++) { if (tempNum[i] == 1 && tempNum[i + 1] == 0) { tempNum[i] = 0; tempNum[i + 1] = 1; pose = i; break; } } // 同时将其左边的所有“1”全部移动到数组的最左端。 for (int i = 0; i < pose; i++) { if (tempNum[i] == 1) sum++; } for (int i = 0; i < pose; i++) { if (i < sum) tempNum[i] = 1; else tempNum[i] = 0; } // 判断是否为最后一个组合:当第一个“1”移动到数组的m-n的位置,即n个“1”全部移动到最右端时,就得到了最后一个组合。 flag = false; for (int i = n - m; i < n; i++) { if (tempNum[i] == 0) flag = true; } int[] a = tempNum.clone(); list.add(a); } while (flag); return list; } public void displayCombinationObject(String[] object, int n, int m) { System.out.println("------" + n + "中选" + m + "的组合的种类总数有:" + countOfCombination(n, m) + "--------"); List<int[]> list = generateCombination(n, m); String s = ""; int j; int[] a; for (int i = 0; i < list.size(); i++) { a = list.get(i); for (j = 0; j < a.length; j++) { if (1 == a[j]) { s += (" " + object[j]); } } System.out.println(s); s = ""; } } //返回n中选m的组合的种类总数 public int countOfCombination(int n, int m) { int sum = 1; int div = 1; for (int i = n; i >= (n - m + 1); i--) { sum *= i; } for (int j = 1; j <= m; j++) { div *= j; } return sum / div; } public static void main(String[] args) { Combination pac = new Combination(); String[] object5 = { "A", "B", "C", "D", "E" }; pac.displayCombinationObject(object5, 5, 3); } }