1


1

http://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order[Wikipedia]から:

辞書式順序の生成

0≦k <nであるすべての数kに対して、次のアルゴリズムは初期シーケンスsjの対応する辞書編集置換、j = 1、…、nを生成する。

関数置換(k、s){var int n:= length(s);階乗:= 1; j = 2からn-1まで{//(n-1)を計算する。 階乗:=階乗* j; j = 1〜n − 1の場合{tempj:=(k /階乗)mod(n − j)。 i = j tempjからj 1 step - 1の場合、= s [j tempj]である。 //チェーンを右にシフト} s [j]:= temps;階乗:=階乗/(n  -  j); sを返します。 }

この背後にある*ロジック*は何ですか? どのように動作しますか?

3 Answer


3


次の次元を持つ、n個のアイテムのすべての順列の多次元配列を考えます。

p [n] [n-1] [n-2] …​ [1]

多次元配列は、次元の1次元配列に線形化できます。

a [n (n-1)(n-2)* …​ * 1]

それは可変基数のようなものです。数値順に並べると、数字の辞書順がわかります。

タプルを参照するために使用するインデックスx [n] = `(i [n]、i [n-1] …​、i [0])`は `sum_j i [j] *(j!)`です

そのため、div / modはタプルから次のポジションを取り戻しています。

タプル内のk番目のインデックスの値は、右端の次元の積で、たまたまk!です。


2


あなたが整数 `x`を持っていて、そして何桁が何桁にあるか知りたいと想像してください。 (例えば。 x = 4723の場合、答えが欲しい7)これを計算するには、まず100で割り、小数部分を捨てます。 (この例では、47が残ります。)次に、10で割ったときの余りを求めます。

今、千の位で桁の値を見つけたいとしましょう。 最初に1000で割って小数部分を捨て、次に10で割ったときに残りを見つけます。

通常の10進法では、各桁は10個の値のうちの1つを保持します。 あなたは私たちの数字発見の練習で私たちが最初に私たちが気にしているものの右側の場所にある値の可能な組み合わせの数で割ることを観察することができる(最初の例では10 * 10)。 それから私達は私達が気にする場所のための可能な値の数で割るとき残りを見つける。 もちろん、_すべての場所には10個の可能な値があるので、10で割るだけです。

さて、各場所が異なる数の値を保持する番号付けシステムを想像してください。 一番右の場所には、0または1の2つの値があります。 次の場所には、0、1、または2の3つの値があります。等々。 このシステムでは、次のように数えます。

0 1 10 11 20 21 100 101 110 111 120 121 200 201 210 211 220
...

これは、wrang-wrangが「可変基数」によって意味するものです。

これで、このシステムのある場所で桁がどのように計算されるかがわかります。 一番右にあるものを見つけるために、最初に分割する必要はありません。そして、その列の桁に2つの可能な値があるので、2を法とする剰余を見つけます。 左側の次の列を見つけるには、まず右側の列にある桁の可能な組み合わせの数で割ります。2つの桁がある列は1つだけなので、2で割ります。 この列には3つの値がある可能性があるため、残りの3を法とします。 左に進み、3番目の列については6で割り(右側の列にはそれぞれ3と2の可能性があり、6になるように乗算)、次にこの列には4つの可能な値があるので剰余4を法とします。

関数を見てみましょう。

関数置換(k、s){var int n:= length(s);階乗:= 1; j = 2からn-1まで{//(n-1)を計算する。 階乗:=階乗* j; }

`factorial`は(n-1)で始まります!

j = 1からn-1の場合{

ここに来るたびに、 factorial`は(n-j)と等しくなります。 `j = 1なので、これは最初のラウンドでは明らかで、` factorial`を(n-1)に初期化したことがわかります。 後で見ますが、 `factorial`は常に(n-j)です。

tempj:=(k /階乗)mod(n 1-j);

ここで、 k 'を factorial`((n-j)に等しい!)で除算して余りを捨ててから、結果を(n 1-j)で除算したときに余りを取ります。 ちょっと待ってください、私が始めたそのすべての愚痴はおなじみのように聞こえ始めています! 左からn番目の列の「数字」の値は、「可変基数システム」を使って求めているところです。

この次のビットは、インデックス「j」と「j tempj」の間の要素のシーケンスを取り、それを右に回転させます。 最後のインデックスを除いて、すべての要素は1つ上のインデックスに移動します。 位置jの右側にあるすべての数字が正しいことを認識することが重要です。 それらのうちの1つを効果的に引き抜き、残りを順番に並べるために一緒に動かしています。 どれを取り出すかは tempj`に依存します。 `tempj`が0のとき、最小のものを選び(そして実際には何もする必要はありません)、 tempj`がn-jのとき、最大のものを選びます。

i = j tempjからj 1 step - 1の場合、= s [j tempj]である。 //チェーンを右にシフト} s [j]:= temps;

次に、(n − j)! (n-j)で割ると(n-j-1)が得られます。 考えてみれば、これはループの先頭に戻って j`が1つ増えたときに factorial`が再び等しくなることを意味します(n-j)。

階乗:=階乗/(n  -  j); sを返します。 }

それが少し役立つことを願っています!


1


初期シーケンスがn = 6のa [] = \ {1,2,3,4,5,6}であるとします。そしてk番目のパーマを生成したい。 最初の場所に1を指定すると、uを5に生成できます。 (すなわち (n - 1)! )残りの場所にパーマをかけます。

1、.......

それから、1と2を交換し、また5を生成することができます。 パーマ。

2、.......

そのため、アイデアはkになります。kの範囲を見つける必要があります。 私が言っているのは、kが225で、5が5だということです。 kはありますか:245/5! = 2したがって、k = 245の場合、生成する置換では、最初の場所は必ず3(つまり a [2])(2 * 5になってからbcoz! = 240、1と3を交換します)

3,1,2,4,5,6(チェインをシフトした後に得られる配列a [])(なぜシフトするのかは、残りの配列を次の反復のためにソートして辞書順を維持するためです)。

だからこそアルゴでは、k /(n-1)をやってね! 最初の繰り返しで。 そして剰余k = k mod(n − 1)!を得る。 これはkの新しい値で、uは(n-j)と同じことを再帰的に実行します。 残りの場所で。