n個の要素の順列を説明するには、最初の要素が終わる位置にn個の可能性があることがわかります。そのため、これを0からn-1までの数で説明できます。次の要素が終了する位置については、n-1個の可能性があるので、0とn-2の間の数でこれを説明できます。
n個の数になるまでなど。
N = 5のための例として、もたらし順列を検討abcde
するがcaebd
。
a
、最初の要素は2番目の位置で終わるため、インデックス1を割り当てます。
b
4番目の位置(インデックス3)で終わりますが、残りの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から右端の数字をカウントします。同様に、「最初の」数字について話すとき、私は右端を意味します。)
その理由桁の重みは、このパターンに従う理由は、k個の0からの数字で表すことができる最大数だけ桁K + 1を使用して表すことができる最小数よりも正確に1低くなければならないということです。2進数では、0111は1000未満である必要があります。10進数では、099999は100000未満である必要があります。
変数ベースへのエンコード
後続の数値間の間隔が正確に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の場合、正確には異なる順列の数です。したがって、エンコードされた数値がすべての可能な順列を完全に指定していることがわかります。
変数ベースのデコードからの
デコードは、バイナリまたは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²)であることに注意してください。順列を数回適用する必要がある場合は、まずそれを共通の表現に変換します。