n 個の要素の順列を記述するには、最初の要素が終了する位置について、n 個の可能性があることがわかります。したがって、0 から n-1 までの数でこれを記述できます。次の要素がたどり着く位置は、n-1通りの可能性があるので、0からn-2までの数字で記述できます。
n個の数字が得られるまでなど。
n = 5 の例として、 をもたらす順列を考えてみましょabcde
うcaebd
。
a
最初の要素である が 2 番目の位置になるため、インデックス1を割り当てます。
b
インデックス 3 となる 4 番目の位置になりますが、残り 3 番目であるため、2を割り当てます。
c
常に0である最初の残りの位置で終了します。
d
(残りの 2 つの位置のうち) 1である最後の残りの位置で終了します。
e
0でインデックス付けされた唯一の残りの位置で終了します。
したがって、インデックス シーケンスは{1, 2, 0, 1, 0}です。
たとえば、2 進数では、「xyz」は z + 2y + 4x を意味することがわかりました。10 進数の場合は、
z + 10y + 100x です。各桁に重みが掛けられ、結果が合計されます。重みの明らかなパターンはもちろん、重みが w = b^k であり、b が数値の基数、k が数字のインデックスであることです。(私は常に右から数字を数え、一番右の数字のインデックス 0 から始めます。同様に、「最初の」数字について話すときは、一番右の数字を意味します。)
数字の重みがこのパターンに従う理由は、0 から k までの数字で表すことができる最大の数は、数字 k+1 のみを使用して表すことができる最小の数よりも正確に 1 小さい必要があるためです。2 進数では、0111 は 1000 より 1 小さい値でなければなりません。10 進数では、099999 は 100000 より 1 小さい値でなければなりません。
variable-base へのエンコード
後続の数値の間隔が正確に 1 であることが重要なルールです。これを実現すると、インデックス シーケンスを可変基数で表すことができます。各桁のベースは、その桁のさまざまな可能性の量です。10 進数の場合、各桁には 10 個の可能性があります。このシステムでは、右端の桁には 1 個の可能性があり、左端には n 個の可能性があります。ただし、右端の数字 (シーケンスの最後の数字) は常に 0 であるため、省略します。つまり、基数 2 から n が残っているということです。一般に、k 番目の数字の基数は b[k] = k + 2 です。数字 k に許可される最大値は、h[k] = b[k] - 1 = k + 1 です。
数字の重み w[k] に関するルールでは、h[i] * w[i] の合計 (i = 0 から i = k まで) が 1 * w[k+1] に等しい必要があります。繰り返しますが、w[k+1] = w[k] + h[k] * w[k] = w[k]*(h[k] + 1) です。最初の重み w[0] は常に 1 である必要があります。そこから開始すると、次の値になります。
k h[k] w[k]
0 1 1
1 2 2
2 3 6
3 4 24
... ... ...
n-1 n n!
(一般的な関係 w[k-1] = k! は、帰納法によって簡単に証明されます。)
シーケンスを変換して得られる数値は、s[k] * w[k] の合計になります。k は 0 から n-1 までです。ここで、s[k] は、シーケンスの k 番目 (右端、0 から始まる) の要素です。例として、{1, 2, 0, 1, 0} を取り上げます。前述のように、右端の要素が取り除かれています: {1, 2, 0, 1}。合計は 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37です。
すべてのインデックスの最大位置を取得すると、{4, 3, 2, 1, 0} になり、119 に変換されることに注意してください。番号エンコーディングの重みは、スキップしないように選択されているためです。任意の数字、0 ~ 119 のすべての数字が有効です。これらは正確に 120 個あり、n! この例では n = 5 であり、正確には異なる順列の数です。したがって、エンコードされた数値がすべての可能な順列を完全に指定していることがわかります。
変数ベースからの
デコード デコードは、2 進数または 10 進数への変換に似ています。一般的なアルゴリズムは次のとおりです。
int number = 42;
int base = 2;
int[] bits = new int[n];
for (int k = 0; k < bits.Length; k++)
{
bits[k] = number % base;
number = number / base;
}
可変基数の場合:
int n = 5;
int number = 37;
int[] sequence = new int[n - 1];
int base = 2;
for (int k = 0; k < sequence.Length; k++)
{
sequence[k] = number % base;
number = number / base;
base++; // b[k+1] = b[k] + 1
}
これにより、37 が {1, 2, 0, 1} に正しくデコードされます (このコード例でsequence
はそう{1, 0, 2, 1}
ですが、適切にインデックスを作成している限りは何でも構いません)。元のシーケンス {1, 2, 0, 1, 0} に戻すには、右端に 0 を追加するだけです (最後の要素には新しい位置の可能性が常に 1 つしかないことに注意してください)。
インデックス シーケンス
を使用したリストの並べ替え 以下のアルゴリズムを使用して、特定のインデックス シーケンスに従ってリストを並べ替えることができます。残念ながら、これは O(n²) アルゴリズムです。
int n = 5;
int[] sequence = new int[] { 1, 2, 0, 1, 0 };
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
bool[] set = new bool[n];
for (int i = 0; i < n; i++)
{
int s = sequence[i];
int remainingPosition = 0;
int index;
// Find the s'th position in the permuted list that has not been set yet.
for (index = 0; index < n; index++)
{
if (!set[index])
{
if (remainingPosition == s)
break;
remainingPosition++;
}
}
permuted[index] = list[i];
set[index] = true;
}
順列の一般的な表現
通常は、これまで行ってきたように直観的に順列を表現することはなく、順列が適用された後の各要素の絶対位置によって単純に表現します。abcde
この例のtoの {1, 2, 0, 1, 0}caebd
は、通常 {1, 3, 0, 4, 2} で表されます。0 から 4 (または一般的には 0 から n-1) の各インデックスは、この表現では 1 回だけ発生します。
この形式で順列を適用するのは簡単です。
int[] permutation = new int[] { 1, 3, 0, 4, 2 };
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
for (int i = 0; i < n; i++)
{
permuted[permutation[i]] = list[i];
}
それを反転することは非常に似ています:
for (int i = 0; i < n; i++)
{
list[i] = permuted[permutation[i]];
}
表現から共通表現への変換
アルゴリズムを使用して、インデックス シーケンスを使用してリストを並べ替え、それを単位順列 {0, 1, 2, ..., n-1} に適用すると、次のようになることに注意してください。一般的な形式で表される逆順列。(この例では {2, 0, 4, 1, 3} )。
反転されていないプレミューテーションを取得するには、先ほど示した順列アルゴリズムを適用します。
int[] identity = new int[] { 0, 1, 2, 3, 4 };
int[] inverted = { 2, 0, 4, 1, 3 };
int[] normal = new int[n];
for (int i = 0; i < n; i++)
{
normal[identity[i]] = list[i];
}
または、逆順列アルゴリズムを使用して、順列を直接適用することもできます。
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
int[] inverted = { 2, 0, 4, 1, 3 };
for (int i = 0; i < n; i++)
{
permuted[i] = list[inverted[i]];
}
一般的な形式で順列を処理するためのすべてのアルゴリズムは O(n) ですが、この形式で順列を適用するのは O(n²) であることに注意してください。順列を複数回適用する必要がある場合は、最初にそれを共通の表現に変換します。