给定表示置换原子的N个元素的数组,是否有类似的算法:
function getNthPermutation( $atoms, $permutation_index, $size )
其中$atoms
是元素数组,$permutation_index
是排列的索引,$size
是排列的大小。
例如:
$atoms = array( 'A', 'B', 'C' );
// getting third permutation of 2 elements
$perm = getNthPermutation( $atoms, 3, 2 );
echo implode( ', ', $perm )."\n";
会打印:
B, A
直到$ permutation_index才计算每个排列?
我听到了关于事实排列的一些事情,但我发现的每一个实现都会给出一个具有相同V大小的排列,这不是我的情况。
感谢。
答案 0 :(得分:43)
正如RickyBobby所说,在考虑排列的词典顺序时,你应该利用因子分解。
从实际的角度来看,这就是我的看法:
(n-1)!
,(n-2)!
开始,等等。i
- 商应该是0
和n-i-1
之间的数字,其中i
从0
到n-1
。以下C代码应该让您了解其工作原理(n
是条目数,i
是排列的索引):
/**
* @param n The number of entries
* @param i The index of the permutation
*/
void ithPermutation(const int n, int i)
{
int j, k = 0;
int *fact = (int *)calloc(n, sizeof(int));
int *perm = (int *)calloc(n, sizeof(int));
// compute factorial numbers
fact[k] = 1;
while (++k < n)
fact[k] = fact[k - 1] * k;
// compute factorial code
for (k = 0; k < n; ++k)
{
perm[k] = i / fact[n - 1 - k];
i = i % fact[n - 1 - k];
}
// readjust values to obtain the permutation
// start from the end and check if preceding values are lower
for (k = n - 1; k > 0; --k)
for (j = k - 1; j >= 0; --j)
if (perm[j] <= perm[k])
perm[k]++;
// print permutation
for (k = 0; k < n; ++k)
printf("%d ", perm[k]);
printf("\n");
free(fact);
free(perm);
}
例如,ithPermutation(10, 3628799)
按预期打印十个元素的最后一个排列:
9 8 7 6 5 4 3 2 1 0
答案 1 :(得分:28)
这是一个允许选择排列大小的解决方案。例如,除了能够生成10个元素的所有排列之外,它还可以生成10个元素中的对的排列。它还会置换任意对象的列表,而不仅仅是整数。
这是PHP,但也有JavaScript和Haskell强制执行。
function nth_permutation($atoms, $index, $size) {
for ($i = 0; $i < $size; $i++) {
$item = $index % count($atoms);
$index = floor($index / count($atoms));
$result[] = $atoms[$item];
array_splice($atoms, $item, 1);
}
return $result;
}
用法示例:
for ($i = 0; $i < 6; $i++) {
print_r(nth_permutation(['A', 'B', 'C'], $i, 2));
}
// => AB, BA, CA, AC, BC, CB
它是如何运作的?
背后有一个非常有趣的想法。我们来看清单A, B, C, D
。我们可以通过从卡片中抽取元素来构造排列。最初我们可以绘制四个元素中的一个。然后是其余三个元素中的一个,依此类推,直到最后我们什么都没有留下。
这是一种可能的选择顺序。从顶部开始,我们采取第三条路径,然后是第一条路径,第二条路径,最后是第一条路径。这就是我们的排列#13。
考虑一下如果选择这个序列,你会在算法上得到十三个数字。然后反转你的算法,这就是你如何从整数重构序列。
让我们试着找到一个将选择序列打包成没有冗余的整数的一般方案,然后将其解包。
一个有趣的方案叫做十进制数系统。 “27”可以被认为是从10中选择路径#2,然后从10中选择路径#7。
但每个数字只能编码来自10个替代品的选项。具有固定基数的其他系统,如二进制和十六进制,也只能编码来自固定数量的备选方案的选择序列。我们想要一个具有可变基数的系统,类似于时间单位,“14:05:29”是小时14从24,分钟5从60,第二个29从60。
如果我们采用通用的数字到字符串和字符串到数字的功能,并欺骗它们使用混合基数怎么办?它们不是采用单个基数,如parseInt('beef', 16)和(48879).toString(16),而是每个数字都需要一个基数。
function pack(digits, radixes) {
var n = 0;
for (var i = 0; i < digits.length; i++) {
n = n * radixes[i] + digits[i];
}
return n;
}
function unpack(n, radixes) {
var digits = [];
for (var i = radixes.length - 1; i >= 0; i--) {
digits.unshift(n % radixes[i]);
n = Math.floor(n / radixes[i]);
}
return digits;
}
这甚至有用吗?
// Decimal system
pack([4, 2], [10, 10]); // => 42
// Binary system
pack([1, 0, 1, 0, 1, 0], [2, 2, 2, 2, 2, 2]); // => 42
// Factorial system
pack([1, 3, 0, 0, 0], [5, 4, 3, 2, 1]); // => 42
现在倒退了:
unpack(42, [10, 10]); // => [4, 2]
unpack(42, [5, 4, 3, 2, 1]); // => [1, 3, 0, 0, 0]
这太美了。现在让我们将这个参数数字系统应用于排列问题。我们将考虑A, B, C, D
的长度为2的排列。它们的总数是多少?让我们看看:首先我们绘制4个项目中的一个,然后是其余3个中的一个,即4 * 3 = 12
方式绘制2个项目。这12种方式可以打包成整数[0..11]。所以,让我们假装我们已经打包它们,并尝试解压缩:
for (var i = 0; i < 12; i++) {
console.log(unpack(i, [4, 3]));
}
// [0, 0], [0, 1], [0, 2],
// [1, 0], [1, 1], [1, 2],
// [2, 0], [2, 1], [2, 2],
// [3, 0], [3, 1], [3, 2]
这些数字表示选择,而不是原始数组中的索引。 [0,0]并不意味着采用A, A
,这意味着从A, B, C, D
(即A)中获取项目#0,然后从剩余列表B, C, D
中获取项目#0(即B) 。结果排列为A, B
。
另一个例子:[3,2]意味着从A, B, C, D
(即D)中获取项目#3,然后从剩余列表A, B, C
中获取项目#2(即C)。结果排列为D, C
。
此映射称为Lehmer code。让我们将所有这些Lehmer代码映射到排列:
AB, AC, AD, BA, BC, BD, CA, CB, CD, DA, DB, DC
这正是我们所需要的。但是如果你看一下unpack
函数,你会注意到它从右到左产生数字(以反转pack
的动作)。 3中的选择在从4中选择之前被解压缩。这是不幸的,因为我们想在从3中选择之前从4个元素中进行选择。如果不能这样做,我们必须首先计算Lehmer代码,将其累积到临时数组中,然后将其应用于项目数组以计算实际排列。
但是如果我们不关心字典顺序,我们可以假装我们要在选择4之前从3个元素中进行选择。然后从unpack
首先选择4。换句话说,我们将使用unpack(n, [3, 4])
代替unpack(n, [4, 3])
。这个技巧允许计算Lehmer代码的下一个数字并立即将其应用于列表。这就是nth_permutation()
的工作方式。
我要提到的最后一件事是unpack(i, [4, 3])
与阶乘数系统密切相关。再看一下第一棵树,如果我们想要长度为2而没有重复的排列,我们可以跳过每一秒的排列索引。这将给我们12个长度为4的排列,可以修剪为长度为2。
for (var i = 0; i < 12; i++) {
var lehmer = unpack(i * 2, [4, 3, 2, 1]); // Factorial number system
console.log(lehmer.slice(0, 2));
}
答案 2 :(得分:15)
这取决于你的方式&#34;排序&#34;你的排列(例如词典顺序)。
一种方法是factorial number system,它会在[0,n!]和所有排列之间给你一个双射。
然后对于[0,n!]中的任何数字,你可以计算第i个排列而不计算其他数字。
这种因子写作基于以下事实:[0和n!]之间的任何数字都可写为:
SUM( ai.(i!) for i in range [0,n-1]) where ai <i
(它与基本分解非常相似)
有关此分解的更多信息,请查看此主题:https://math.stackexchange.com/questions/53262/factorial-decomposition-of-integers
希望有所帮助
正如此wikipedia article所述,此方法相当于计算lehmer code:
生成n的排列的一种显而易见的方法是生成值 Lehmer代码(可能使用阶乘数系统) 表示整数到n!),并将它们转换为 相应的排列。然而,后一步,而 直截了当,难以有效实施,因为它需要 n从序列中选择每个选项并从中删除, 处于任意位置;的明显表示 序列作为数组或链表,都需要(针对不同的 关于执行转换的n2 / 4操作的原因)。随着n 可能相当小(特别是如果所有的一代 需要排列)这不是一个问题,而是它 事实证明,无论是随机还是系统生成都有 简单的替代品,做得更好。出于这个原因 虽然肯定有可能采用一种特殊的方法,但似乎并没有用 允许从Lehmer执行转换的数据结构 代码在O(n log n)时间内排列。
因此,对于一组n元素,你可以做的最好的是O(n ln(n)),它具有适应的数据结构。
答案 3 :(得分:7)
这是一种在线性时间内在排列和排名之间进行转换的算法。但是,它使用的排名不是词典。这很奇怪,但一致。我将给出两个函数,一个从一个等级转换为一个置换,另一个用于反转。
首先,取消(从排名到排列)
Initialize:
n = length(permutation)
r = desired rank
p = identity permutation of n elements [0, 1, ..., n]
unrank(n, r, p)
if n > 0 then
swap(p[n-1], p[r mod n])
unrank(n-1, floor(r/n), p)
fi
end
接下来,排名:
Initialize:
p = input permutation
q = inverse input permutation (in linear time, q[p[i]] = i for 0 <= i < n)
n = length(p)
rank(n, p, q)
if n=1 then return 0 fi
s = p[n-1]
swap(p[n-1], p[q[n-1]])
swap(q[s], q[n-1])
return s + n * rank(n-1, p, q)
end
这两者的运行时间是O(n)。
有一篇很好的,可读的论文解释了为什么这样有效:排名&amp;通过Myrvold&amp; amp;和线性时间中的排名排列。 Ruskey,信息处理快报,第79卷,第6期,2001年9月30日,第281-284页。
http://webhome.cs.uvic.ca/~ruskey/Publications/RankPerm/MyrvoldRuskey.pdf
答案 4 :(得分:5)
这是python中的一个简短且非常快(元素数量为线性)的解决方案,适用于任何元素列表(下例中的13个首字母):
from math import factorial
def nthPerm(n,elems):#with n from 0
if(len(elems) == 1):
return elems[0]
sizeGroup = factorial(len(elems)-1)
q,r = divmod(n,sizeGroup)
v = elems[q]
elems.remove(v)
return v + ", " + ithPerm(r,elems)
示例:
letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m']
ithPerm(0,letters[:]) #--> a, b, c, d, e, f, g, h, i, j, k, l, m
ithPerm(4,letters[:]) #--> a, b, c, d, e, f, g, h, i, j, m, k, l
ithPerm(3587542868,letters[:]) #--> h, f, l, i, c, k, a, e, g, m, d, b, j
注意:我提供letters[:]
(letters
的副本)而不是字母,因为该函数会修改其参数elems
(删除所选元素)
答案 5 :(得分:2)
以下代码计算给定n的第k个排列。
即n = 3。 各种排列是 123 132 213 231 312 321
如果k = 5,则返回312。 换句话说,它给出了第k个字典编排。
#define OPT_STRING_BASE "haspvb"
#ifdef HAVE_WIFI
#define OPT_STRING_WIFI "mw"
#else
#define OPT_STRING_WIFI
#endif // HAVE_WIFI
#ifdef HAVE_IMEI
#define OPT_STRING_IMEI "i"
#else
#define OPT_STRING_IMEI
#endif // HAVE_IMEI
#define OPT_STRING (OPT_STRING_BASE OPT_STRING_WIFI OPT_STRING_IMEI)
答案 6 :(得分:0)
可以计算。这是一个为您完成的C#代码。
#If
答案 7 :(得分:-1)
如果将所有排列存储在内存中,例如存储在数组中,您应该能够在O(1)时间内将它们一次退出。
这意味着您必须存储所有排列,因此如果计算所有排列需要花费相当长的时间,或者存储它们需要相当大的空间,那么这可能不是解决方案。
我的建议是尝试它,如果它太大/太慢,那就回来吧 - 如果一个天真的人能够完成这项工作,那就没有必要寻找一个“聪明”的解决方案。