来自Wikipedia:
字典顺序生成
对于每个数k,0≤k<0。 ñ! 以下算法生成 相应的词典学 初始序列的排列 sj,j = 1,...,n:
function permutation(k, s) { var int n:= length(s); factorial:= 1; for j= 2 to n- 1 { // compute (n- 1)! factorial:= factorial* j; } for j= 1 to n- 1 { tempj:= (k/ factorial) mod (n+ 1- j); temps:= s[j+ tempj] for i= j+ tempj to j+ 1 step -1 { s[i]:= s[i- 1]; // shift the chain right } s[j]:= temps; factorial:= factorial/ (n- j); } return s; }
这背后的逻辑是什么? 如何运作?
答案 0 :(得分:3)
考虑一个多维数组,包含n个项目的所有排列,其维度为:
p[n][n-1][n-2]...[1]
任何多维数组都可以线性化为维度的1d数组:
a[n*(n-1)*(n-2)*...*1]
它就像一个可变基数;按数字顺序排列会给出数字上的字典顺序。
用于引用元组x [n] =的索引(i[n],i[n-1]...,i[0])
为sum_j i[j]*(j!)
所以,除法/ mod正在从元组中恢复下一个位置。
元组中第k个索引的值是其右边的维度的乘积,恰好是k !.
答案 1 :(得分:2)
想象一下,您有一个整数x
,并且您想知道数百个位置中的数字。 (例如,如果x = 4723,你想得到答案7.)为了计算这个,你首先除以100,丢掉小数部分。 (在我们的例子中,这会留下47.)然后在除以10时找到余数。
现在假设您要在千位中找到数字的值。为了找到你首先除以1000,丢掉小数部分,然后在除以10时再次找到余数。
在常规十进制编号系统中,每个地方都包含10个值中的一个。您可以观察到,在我们的数字查找练习中,我们首先除以我们关心的位置右侧的值的可能组合的数量(在第一个示例中为10 * 10)。然后我们找到余数除以我们关心的地方的可能值的数量。当然,所有个地方都有10个可能的值,所以我们只需要除以10。
现在,想象一个编号系统,其中每个地方拥有不同数量的值。我们最右边的地方可以有两个值,0或1.下一个地方可以有三个值,0,1或2;等等。在这个系统中我们算是这样的:
0
1
10
11
20
21
100
101
110
111
120
121
200
201
210
211
220
...
这就是“变基数”所谓的争吵。
现在,您可以看到我们如何计算此系统中某个位置的数字。为了找到最右边的,我们不需要先划分,我们发现余数是模2,因为该列中的数字有2个可能的值。要找到左边的下一列,我们首先除以右边列中数字的可能组合数:只有一列有两个可能的数字,所以我们除以2.然后我们取余数模3,因为此列有三个可能的值。继续向左,对于第3列,我们除以6(因为右边的列各有3个和2个可能性,乘以6)然后取余数模4,因为此列中有4个可能的值。
让我们来看看这个函数:
function permutation(k, s) {
var int n:= length(s); factorial:= 1;
for j= 2 to n- 1 { // compute (n- 1)!
factorial:= factorial* j;
}
factorial
以(n-1)开头!
for j= 1 to n- 1 {
每次我们到达这里,factorial
等于(n-j)!这是第一次显而易见的,因为j
= 1,我们知道我们将factorial
初始化为(n-1)!我们稍后会看到factorial
确实总是(n-j)!
tempj:= (k/ factorial) mod (n+ 1- j);
这里我们将k
除以factorial
(等于(nj)!)然后扔掉剩余部分,然后当我们将结果除以(n + 1-j)时我们取剩)。等一下,我开始的所有唠叨开始听起来很熟悉!我们只是使用我们的“可变基数系统”从左边的第n列中找到“数字”的值!
下一位采用索引j
和j + tempj
之间的元素序列并向右旋转 - 即每个元素向上移动一个索引,除了最后一个索引,它将移回到开始。重要的是要意识到位置j右侧的所有数字都是有序的。我们正在有效地将其中一个拔出并推动其余部分以保持秩序。我们采摘的是哪一个取决于tempj
。当tempj
为0时,我们选择最小的(实际上不需要做任何推动),当tempj
等于n-j时,我们选择最大的。
temps:= s[j+ tempj]
for i= j+ tempj to j+ 1 step -1 {
s[i]:= s[i- 1]; // shift the chain right
}
s[j]:= temps;
接下来,(n-j)!除以(n-j)给出(n-j-1)!
如果你考虑一下,你应该看到这意味着当我们回到循环顶部并且j
增加1时,factorial
将再次等于(nj)!
factorial:= factorial/ (n- j);
}
return s;
}
我希望有所帮助!
答案 2 :(得分:1)
假设您的初始序列为n = 6的[] = {1,2,3,4,5,6};你想生成第k个烫发。 在Ist地方有1个,你可以产生5个! (即(n-1)!)烫发剩余的地方。
1 ,.......
然后u xchange 1和2再次你可以再次生成5!烫发。
2 ,.......
所以给出k的想法,我们需要找到k的范围。我的意思是: 说k是225,多少5! k有:245/5! = 2 因此,如果k = 245,在我想要生成的排列中,第一位肯定是 3(即a [2])(bcoz后2 * 5!= 240,我将x换1和3),我会有
3,1,2,4,5,6 (the array a[] obtained after shifting the chain)
(why we are shifting is to make the remaining array sorted for the
next iteration so that lexicographic order is maintained.)
这就是为什么在算法中,你做k /(n-1)!在第一次迭代中。 并获得余数k = k mod(n-1)!。这是k的新值,你用(n-j)递归地做同样的事情!在其余的地方。