我把这段代码放在一个控制台应用程序中编译之后,执行了如下命令:
Combination c = new Combination(5,3); Console.WriteLine("\nCombination c(5,3) is initially " + c.ToString()); |
Combination c(5,3) is initially { 0 1 2 } |
当程序执行第一条语句时:
Combination c = new Combination(5,3); |
这表示从5个元素一次选取3个的组合,这时在内存中已经生成了按字典排序的组合中的第一个元素。见图4。
图4 内存中的对象
构造函数快速地创建了组合中的第一个元素。因为我们要处理的数据的值可能很大,所以我选择用C#的长整型long代替短整型int,如果还想扩大数值范围,我们可以用无符号长整型ulong。我用从0到k-1的k个整数填充data的数组。
计算组合的总数
接下来我们将讨论,如何计算组合的总数。例如当n=5,k=3时共有10种组合:
{ 0, 1, 2 } { 0, 3, 4 } { 0, 1, 3 } { 1, 2, 3 } { 0, 1, 4 } { 1, 2, 4 } { 0, 2, 3 } { 1, 3, 4 } { 0, 2, 4 } { 2, 3, 4 } |
标准的Choose(n,k)函数直接用的是组合公式的定义,虽然标准,但是解决问题的能力很有限,代码如下:
// poor implementation of Choose(n,k) static int Choose(int n, int k) { int numerator = Factorial(n); int denominator = Factorial(k) * Factorial(n-k); return ( numerator / denominator ); } |
其中Factorial是计算阶乘:
static int Factorial(int m) { int ans = 1; for (int i = m; i >= 1; —-i) { ans = ans * i; } return ans; } |
这个Choose函数有个很严重的问题,即使是不大的n和k都有可能造成堆栈溢出。仔细观察这个Choose函数,它先计算了n的阶乘,这就很容易得到一个巨大的值,即使n很小(例如,n=21,试试计算21!),用64位无符号整型的变量存储很快便会溢出。Choose(n,k)要计算的其实是两个数的商,这两个数都可能很大,但是他们的商却相对小很多。这个方法的问题就是:即使最后我们计算出的结果很小,但是在计算的过程中却会产生很大的数而导致溢出。