这个“词典顺序生成算法”如何工作?

时间:2009-09-19 01:41:21

标签: algorithm language-agnostic permutation

来自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;
 }

这背后的逻辑是什么? 如何运作?

3 个答案:

答案 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列中找到“数字”的值!

下一位采用索引jj + 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)递归地做同样的事情!在其余的地方。