回想一下,n 项有 n! 排列。 n!可以这样理解:
1. There are `n` options for choosing the first item.
2. There are `n-1` items remaining from which to choose the second item
...
n-1. There are `2` options left for the `n-1`-th item.
n. There is only 1 item left for the `n`-th position.
因此,对于如何订购商品,共有(n) * (n-1) * (n-2) * ... (2) * (1) = n! 种选择。
这直接揭示了一种使用混合基数编号方案枚举排列的方法。在这个方案中,最重要的数字将是基数n,下一个最重要的数字将是基数n-1..等。
您可以使用这样的混合基数以下列方式选择排列:
- 使用最高有效位从数组中选择一个元素(注意第一个数字范围从
[0, n-1],并且有n元素可供选择,因此您可以将其用作项的索引来选择。)
- 从数组中删除选定的元素,记录它是置换数组的第一个元素,并将其余元素压缩到数组的前面。
- 使用倒数第二位从剩余的项目中选择一个元素(注意这个数字的取值范围是
[0, n-2],还有n-1位剩余)
- 删除选定元素,将其记录为置换数组中的第二个元素
- 重复直到所有项目都被选中。
如果我们使用数组来表示小端数字顺序的混合基数,那么我们将有以下内容:
int mixed_radix[n] = {0};
您可以通过以下方式增加这个混合基数:
//Increment the least-significant digit
mixed_radix[0]++;
//Ripple overflow toward the most-significant digit
for(i=0; i<n; i++) {
if(mixed_radix[i] > i) {
mixed_radix[i] = 0;
if(i < n-1)mixed_radix[i+1]++;
}
else {
break;
}
}
所以我们有一种方法可以将混合基数初始化为零,然后将它递增到每个可能的值,一旦它回绕到零就停止。 (或在n!-1 递增之后...)
总而言之,有一个可爱的数字技巧可以让这变得更简单:您可以通过以下方式从整数中解压缩那个丑陋的混合基数:
我们知道有n 项的n! 排列;对于[0, n!-1] 范围内的任何整数val,以下提供了整数值和混合基数之间的双射映射:
int working = val; //val in the range [0, n!-1]
for(j=0 j<n; j++) {
mixed_radix[j] = working % (j+1);
working /= (j+1);
}
因此,将这个“解包”循环嵌入到在0 到n!-1 范围内运行val 的外循环中是一种更密集的枚举混合基数(枚举可能的排列)的方法。它还为每个排列分配一个整数,有效地命名它们。 :)