web-dev-qa-db-ja.com

他を計算せずにn番目の順列を見つける

順列アトムを表すN個の要素の配列がある場合、そのようなアルゴリズムはありますか?

function getNthPermutation( $atoms, $permutation_index, $size )

どこ $atomsは要素の配列、$permutation_indexは順列のインデックスであり、$sizeは順列のサイズです。

例えば:

$atoms = array( 'A', 'B', 'C' );
// getting third permutation of 2 elements
$perm = getNthPermutation( $atoms, 3, 2 );

echo implode( ', ', $perm )."\n";

印刷します:

B, A

$ permutation_indexまですべての順列を計算せずに?

階乗置換について何かを聞いたが、私が見つけたすべての実装は結果として同じサイズのVの置換を与えるが、これは私の場合ではない。

ありがとう。

40

RickyBobbyが述べたように、順列の辞書式順序を検討するときは、階乗分解を有利に使用する必要があります。

実用的な観点から、これは私がそれを見る方法です:

  • ユークリッド除算を実行します。ただし、階乗数を使用して、_(n-1)!_、_(n-2)!_などで始まります。
  • 商を配列に保持します。 i番目の商は、_0_から_n-i-1_までの数値である必要があります。iは_0_から_n-1_までです。
  • この配列is順列。問題は、各商が以前の値を考慮しないため、それらを調整する必要があることです。より明確に言えば、以前の値と同じかそれよりも小さい数だけ、すべての値をインクリメントする必要があります。

次のCコードは、これがどのように機能するかを示します(nはエントリの数、iは置換のインデックスです)。

_/**
 * @param n The number of entries
 * @param i The index of the permutation
 */
void ithPermutation(const int n, int i)
{
   int j, k = 0;
   int *fact = (int *)calloc(n, sizeof(int));
   int *perm = (int *)calloc(n, sizeof(int));

   // compute factorial numbers
   fact[k] = 1;
   while (++k < n)
      fact[k] = fact[k - 1] * k;

   // compute factorial code
   for (k = 0; k < n; ++k)
   {
      perm[k] = i / fact[n - 1 - k];
      i = i % fact[n - 1 - k];
   }

   // readjust values to obtain the permutation
   // start from the end and check if preceding values are lower
   for (k = n - 1; k > 0; --k)
      for (j = k - 1; j >= 0; --j)
         if (perm[j] <= perm[k])
            perm[k]++;

   // print permutation
   for (k = 0; k < n; ++k)
      printf("%d ", perm[k]);
   printf("\n");

   free(fact);
   free(perm);
}
_

たとえば、ithPermutation(10, 3628799)は、予想どおり、10個の要素の最後の順列を出力します。

_9 8 7 6 5 4 3 2 1 0
_
45
FelixCQ

順列のサイズを選択できるソリューションは次のとおりです。たとえば、10個の要素のすべての順列を生成できることに加えて、10個の要素間のペアの順列を生成できます。また、整数だけでなく、任意のオブジェクトのリストを並べ替えます。

_function nth_permutation($atoms, $index, $size) {
    for ($i = 0; $i < $size; $i++) {
        $item = $index % count($atoms);
        $index = floor($index / count($atoms));
        $result[] = $atoms[$item];
        array_splice($atoms, $item, 1);
    }
    return $result;
}
_

使用例:

_for ($i = 0; $i < 6; $i++) {
    print_r(nth_permutation(['A', 'B', 'C'], $i, 2));
}
// => AB, BA, CA, AC, BC, CB
_

どのように機能しますか?

その背後には非常に興味深いアイデアがあります。リスト_A, B, C, D_を取り上げましょう。カードのデッキからのように、要素をそこから描画することで順列を構築できます。最初に、4つの要素の1つを描画できます。その後、残りの3つの要素の1つ、というように、最終的には何も残されなくなります。

Decision tree for permutations of 4 elements

以下は、可能な選択肢のシーケンスの1つです。上から3番目のパス、最初のパス、2番目のパス、最後に1番目のパスを使用します。そして、それが私たちの順列#13です。

この一連の選択を前提として、アルゴリズム的に13に到達する方法を考えてみてください。次に、アルゴリズムを逆にします。これにより、整数からシーケンスを再構築できます。

選択肢のシーケンスを冗長性なしで整数にパックし、それをアンパックするための一般的なスキームを見つけてみましょう。

興味深いスキームの1つは、10進数システムと呼ばれます。 「27」は、10からパス#2を選択し、次に10からパス#7を選択すると考えることができます。

Decision three for number 27 in decimal

ただし、各桁は10の選択肢からの選択肢しかエンコードできません。 2進数や16進数などの固定基数を持つ他のシステムも、固定数の選択肢からの選択のシーケンスのみをエンコードできます。時間単位のような変数の基数を持つシステムが必要です。「14:05:29」は、24からの時間14、60からの分5、60からの秒29です。

汎用の数値から文字列への関数と文字列から数値への関数を取り、混合基数を使用するようにそれらをだますとどうなりますか? parseInt( 'beef'、16)(48879).toString(16) のように単一の基数を取る代わりに、各桁ごとに1つの基数を取ります。

_function pack(digits, radixes) {
    var n = 0;
    for (var i = 0; i < digits.length; i++) {
        n = n * radixes[i] + digits[i];
    }
    return n;
}

function unpack(n, radixes) {
    var digits = [];
    for (var i = radixes.length - 1; i >= 0; i--) {
        digits.unshift(n % radixes[i]);
        n = Math.floor(n / radixes[i]);
    }
    return digits;
}
_

それでも機能しますか?

_// Decimal system
pack([4, 2], [10, 10]); // => 42

// Binary system
pack([1, 0, 1, 0, 1, 0], [2, 2, 2, 2, 2, 2]); // => 42

// Factorial system
pack([1, 3, 0, 0, 0], [5, 4, 3, 2, 1]); // => 42
_

そして今後方:

_unpack(42, [10, 10]); // => [4, 2]

unpack(42, [5, 4, 3, 2, 1]); // => [1, 3, 0, 0, 0]
_

これはとても美しいです。次に、このパラメトリック数体系を順列の問題に適用してみましょう。 _A, B, C, D_の長さ2の順列を考慮します。それらの総数はいくつですか?見てみましょう:最初に4つの項目の1つを描画し、次に残りの3つの項目の1つを描画します。つまり、2つの項目を描画する_4 * 3 = 12_方法です。これらの12の方法は、整数[0..11]にパックできます。それで、それらをすでにパックしたと仮定して、アンパックしてみてください:

_for (var i = 0; i < 12; i++) {
    console.log(unpack(i, [4, 3]));
}

// [0, 0], [0, 1], [0, 2],
// [1, 0], [1, 1], [1, 2],
// [2, 0], [2, 1], [2, 2],
// [3, 0], [3, 1], [3, 2]
_

これらの数値は選択肢を表し、元の配列のインデックスではありません。 [0、0]は_A, A_を取得することを意味するのではなく、_A, B, C, D_(つまりA)から項目#0を取得し、残りのリスト_B, C, D_(それはB)から項目#0を取得することを意味します。結果の順列は_A, B_です。

別の例:[3、2]は、_A, B, C, D_(つまりD)から項目#3を取得し、残りのリスト_A, B, C_(つまりC)から項目#2を取得することを意味します。結果の順列は_D, C_です。

このマッピングは Lehmerコード と呼ばれます。これらすべてのレーマーコードを順列にマッピングしましょう。

_AB, AC, AD, BA, BC, BD, CA, CB, CD, DA, DB, DC
_

それがまさに私たちに必要なことです。しかし、unpack関数を見ると、右から左に数字が生成されていることがわかります(packの動作を逆にするため)。 3からの選択は、4からの選択の前にアンパックされます。これは、3から選択する前に4要素から選択したいので、残念です。これができない場合は、まずレーマーコードを計算し、一時配列に蓄積します。そして、それを項目の配列に適用して、実際の順列を計算します。

しかし、辞書式順序を気にしない場合は、4から選択する前に3つの要素から選択したいふりをすることができます。次に、4からの選択がunpackから最初に出てきます。つまり、unpack(n, [3, 4])の代わりにunpack(n, [4, 3])を使用します。このトリックにより、レーマーコードの次の桁を計算し、すぐにリストに適用できます。そして、まさにnth_permutation()が機能する方法です。

最後に述べておきたいのは、unpack(i, [4, 3])は階乗数システムと密接に関連しているということです。もう一度その最初のツリーを見てください。長さ2の順列が重複なしに必要な場合は、2番目ごとの順列インデックスをスキップできます。これにより、長さ4の12の順列が得られ、長さ2にトリミングできます。

_for (var i = 0; i < 12; i++) {
    var lehmer = unpack(i * 2, [4, 3, 2, 1]); // Factorial number system
    console.log(lehmer.slice(0, 2));
}
_
31
Alexey Lebedev

これは、順列を「ソート」する方法(辞書式順序など)によって異なります。

これを行う1つの方法は、 階乗数システム です。[0、n!]とすべての順列の間の全単射を提供します。

次に、[0、n!]の任意の数iについて、他を計算せずにi番目の置換を計算できます。

この階乗の記述は、[0とn!]の間の任意の数が次のように記述できるという事実に基づいています。

SUM( ai.(i!) for i in range [0,n-1]) where ai <i 

(基本分解とかなり似ています)

この分解の詳細については、このスレッドをご覧ください。 https://math.stackexchange.com/questions/53262/factorial-decomposition-of-integers

それが役に立てば幸い


この wikipediaの記事 で述べたように、このアプローチは lehmerコード の計算と同等です。

Nの順列を生成する明白な方法は、(おそらくnまでの整数の階乗数系表現を使用して)レーマーコードの値を生成し、それらを対応する順列に変換することです。ただし、後者の手順は単純ですが、シーケンスからの選択とシーケンスからの削除のそれぞれを任意の位置でn回操作する必要があるため、効率的に実装することは困難です。配列またはリンクされたリストとしてのシーケンスの明白な表現の両方は、変換を実行するために(異なる理由で)約n2/4演算を必要とします。 nはかなり小さい可能性が高いので(特にすべての順列の生成が必要な場合)、それほど問題ではありませんが、ランダム生成と系統的生成の両方で、かなり優れた単純な代替案があることがわかります。このため、確かに可能ではありますが、レーマーコードからO(n log n)時間での置換への変換を可能にする特別なデータ構造を採用することは有用ではないようです。

したがって、n要素のセットに対して実行できる最善の方法は、データ構造を適合させたO(n ln(n))です。

15
Ricky Bobby

線形時間で順列とランクを変換するアルゴリズムを次に示します。ただし、使用するランキングは辞書式ではありません。奇妙ですが、一貫しています。ランクから順列に変換する関数とその逆を行う関数の2つを提供します。

まず、ランクを解除します(ランクから順列に移動します)

Initialize:
n = length(permutation)
r = desired rank
p = identity permutation of n elements [0, 1, ..., n]

unrank(n, r, p)
  if n > 0 then
    swap(p[n-1], p[r mod n])
    unrank(n-1, floor(r/n), p)
  fi
end

次に、ランク付けするには:

Initialize:
p = input permutation
q = inverse input permutation (in linear time, q[p[i]] = i for 0 <= i < n)
n = length(p)

rank(n, p, q)
  if n=1 then return 0 fi
  s = p[n-1]
  swap(p[n-1], p[q[n-1]])
  swap(q[s], q[n-1])
  return s + n * rank(n-1, p, q)
end

これらの両方の実行時間はO(n)です。

これが機能する理由を説明する読みやすい優れた論文があります:線形時間でのランキングとランキング解除順列、Myrvold&Ruskey、情報処理レター第79巻、第6号、2001年9月30日、ページ281–284。

http://webhome.cs.uvic.ca/~ruskey/Publications/RankPerm/MyrvoldRuskey.pdf

8
Dave

以下は、Pythonの短くて非常に高速な(要素数で線形)ソリューションであり、要素のリスト(以下の例の最初の13文字)で機能します。

from math import factorial

def nthPerm(n,elems):#with n from 0
    if(len(elems) == 1):
        return elems[0]
    sizeGroup = factorial(len(elems)-1)
    q,r = divmod(n,sizeGroup)
    v = elems[q]
    elems.remove(v)
    return v + ", " + ithPerm(r,elems)

例:

letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m']

ithPerm(0,letters[:])          #--> a, b, c, d, e, f, g, h, i, j, k, l, m
ithPerm(4,letters[:])          #--> a, b, c, d, e, f, g, h, i, j, m, k, l
ithPerm(3587542868,letters[:]) #--> h, f, l, i, c, k, a, e, g, m, d, b, j

注:letters[:]lettersのコピー)文字ではなく、関数がパラメータelemsを変更するため(選択された要素を削除します)

6
Ismael EL ATIFI

次のコードは、指定されたnのk番目の順列を計算します。

つまり、n = 3です。さまざまな順列は123 132 213 231 312 321です。

K = 5の場合、312を返します。つまり、k番目の辞書式順列を返します。

    public static String getPermutation(int n, int k) {
        char temp[] = IntStream.range(1, n + 1).mapToObj(i -> "" + i).collect(Collectors.joining()).toCharArray();
        return getPermutationUTIL(temp, k, 0);
    }

    private static String getPermutationUTIL(char temp[], int k, int start) {
        if (k == 1)
            return new String(temp);
        int p = factorial(temp.length - start - 1);
        int q = (int) Math.floor(k / p);
        if (k % p == 0)
            q = q - 1;
        if (p <= k) {
            char a = temp[start + q];
            for (int j = start + q; j > start; j--)
                temp[j] = temp[j - 1];
            temp[start] = a;
        }
        return k - p >= 0 ? getPermutationUTIL(temp, k - (q * p), start + 1) : getPermutationUTIL(temp, k, start + 1);
    }

    private static void swap(char[] arr, int j, int i) {
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    private static int factorial(int n) {
        return n == 0 ? 1 : (n * factorial(n - 1));
    }
2

計算可能です。これはあなたのために行うC#コードです。

using System;
using System.Collections.Generic;

namespace WpfPermutations
{
    public class PermutationOuelletLexico3<T>
    {
        // ************************************************************************
        private T[] _sortedValues;

        private bool[] _valueUsed;

        public readonly long MaxIndex; // long to support 20! or less 

        // ************************************************************************
        public PermutationOuelletLexico3(T[] sortedValues)
        {
            if (sortedValues.Length <= 0)
            {
                throw new ArgumentException("sortedValues.Lenght should be greater than 0");
            }

            _sortedValues = sortedValues;
            Result = new T[_sortedValues.Length];
            _valueUsed = new bool[_sortedValues.Length];

            MaxIndex = Factorial.GetFactorial(_sortedValues.Length);
        }

        // ************************************************************************
        public T[] Result { get; private set; }

        // ************************************************************************
        /// <summary>
        /// Return the permutation relative to the index received, according to 
        /// _sortedValues.
        /// Sort Index is 0 based and should be less than MaxIndex. Otherwise you get an exception.
        /// </summary>
        /// <param name="sortIndex"></param>
        /// <param name="result">Value is not used as inpu, only as output. Re-use buffer in order to save memory</param>
        /// <returns></returns>
        public void GetValuesForIndex(long sortIndex)
        {
            int size = _sortedValues.Length;

            if (sortIndex < 0)
            {
                throw new ArgumentException("sortIndex should be greater or equal to 0.");
            }

            if (sortIndex >= MaxIndex)
            {
                throw new ArgumentException("sortIndex should be less than factorial(the lenght of items)");
            }

            for (int n = 0; n < _valueUsed.Length; n++)
            {
                _valueUsed[n] = false;
            }

            long factorielLower = MaxIndex;

            for (int index = 0; index < size; index++)
            {
                long factorielBigger = factorielLower;
                factorielLower = Factorial.GetFactorial(size - index - 1);  //  factorielBigger / inverseIndex;

                int resultItemIndex = (int)(sortIndex % factorielBigger / factorielLower);

                int correctedResultItemIndex = 0;
                for(;;)
                {
                    if (! _valueUsed[correctedResultItemIndex])
                    {
                        resultItemIndex--;
                        if (resultItemIndex < 0)
                        {
                            break;
                        }
                    }
                    correctedResultItemIndex++;
                }

                Result[index] = _sortedValues[correctedResultItemIndex];
                _valueUsed[correctedResultItemIndex] = true;
            }
        }

        // ************************************************************************
        /// <summary>
        /// Calc the index, relative to _sortedValues, of the permutation received
        /// as argument. Returned index is 0 based.
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public long GetIndexOfValues(T[] values)
        {
            int size = _sortedValues.Length;
            long valuesIndex = 0;

            List<T> valuesLeft = new List<T>(_sortedValues);

            for (int index = 0; index < size; index++)
            {
                long indexFactorial = Factorial.GetFactorial(size - 1 - index);

                T value = values[index];
                int indexCorrected = valuesLeft.IndexOf(value);
                valuesIndex = valuesIndex + (indexCorrected * indexFactorial);
                valuesLeft.Remove(value);
            }
            return valuesIndex;
        }

        // ************************************************************************
    }
}
0
Eric Ouellet