他者を計算せずに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まですべての順列を計算することなく?

私はfactoradic順列について何か聞いたことがあるが、私が見つけたすべての実装では、同じ大きさのVの順列が結果として得られる。これは私の場合ではない。

ありがとう。

36
あなたが反復カウンタ(順列0、順列1、順列2、...)でN個の要素のすべての順列を出力すると想像してください...私はn番目の順列を欲しいです。
追加された 著者 Simone Margaritelli,
私は順列の並べ替えを気にしない、どんな仕事をします:)
追加された 著者 Simone Margaritelli,
あなたが注文について気にしないなら、好きな大きさの$ sizeの任意の順列を選ぶことができます。別のインデックスで毎回この関数を何回か呼びたいのですか?
追加された 著者 galchen,
順列の順序を決定するのは何ですか?つまり、インデックス0の順列は、いずれの形式でもかまいません
追加された 著者 galchen,
どのような順列のインデックスを意味するのですか?
追加された 著者 galchen,

7 答え

RickyBobbyが述べたように、順列の辞書的順序を考えるときは、あなたの優位性で階乗分解を使うべきです。

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

  • あなたが(n-1)!(n-2)!で始まる階乗番号を使用する以外は、ある種類のユークリッド部門を実行します。 on。
  • 商を配列に保持します。 i 番目の商は、 0 から ni-1 code> 0 を n-1 に変更します。
  • この配列はあなたの順列である 。問題は、各商が以前の値を気にしないので、それらを調整する必要があるということです。より明示的には、より小さいか等しい以前の値と同じ回数だけ、すべての値を増やす必要があります。

次の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
41
追加された
+1 thxフェリックスの実装:)
追加された 著者 Ricky Bobby,
それはまさに私が探していた実装だった、 'n'引数はキーです...ありがとうsoooo多く:)
追加された 著者 Simone Margaritelli,
factoradic/lehmerコードを取得するためにここで使用された方法(計算された階乗を使用し、余りではなく商を格納する)は、Wikipediaの Factoradic を参照してください。私がテストした出力は同じですが、後者の方が簡単です。それにもかかわらず、あなたの例は私がコンセプトをよりよく理解するのを助けました。
追加された 著者 konsolebox,

順列のサイズを選択できる解決策があります。例えば、10個の要素のすべての順列を生成できることを別にすれば、10個の要素の間でペアの順列を生成することができる。また、整数だけでなく、任意のオブジェクトのリストを置換します。

これはPHPですが、 JavaScriptHaskell

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

Usage example:

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番目のパス、次に1番目のパス、2番目のパス、最後に最初のパスを取っています。それが私たちの順列#13です。

考えてみると、この一連の選択肢を考えれば、アルゴリズム的には13になるでしょう。アルゴリズムを逆にすると、整数からシーケンスを再構成することができます。

冗長性のない整数に選択肢のシーケンスをパックし、それを元に戻す一般的なスキームを見つけようとしましょう。

1つの興味深い方式を10進数システムと呼びます。 "27"は、10のうち2番のパスを選択し、10のうち7番のパスを選択すると考えることができます。

Decision three for number 27 in decimal

しかし、各桁は10の選択肢からの選択肢しかエンコードできません。バイナリや16進のような固定された基数を持つ他のシステムでも、固定数の選択肢からの選択肢のシーケンスしかエンコードできません。私たちは、時間単位のような種類の可変基数を持つシステムが必要です。 "14:05:29"は24時間から14分、60分から5分、60分から29分です。

一般的なnumber-to-string関数とstring-to-number関数を取り、混合基数を使用してそれらを欺くとどうなりますか?単一の基数をとるのではなく、 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つを描画すると、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]

これらの数値は、元の配列のインデックスではなく選択肢を表します。 A、B、C、D (それはA)から項目#0を取ってから項目#0を取ることを意味します。残りのリスト B、C、D (これはBです)から0を返します。結果として得られる置換は A、B です。

もう1つの例:[3,2]は、残りのリスト A、B、C から A、B、C、D (D)/code>(それはCです)。そして、結果の順列は D、C です。

このマッピングは、 Lehmerコードと呼ばれています。これらのすべてのLehmerコードを並べ替えにマップしてみましょう:

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

それがまさに私たちが必要とするものです。しかし、 unpack 関数を見ると、 pack の動作を逆にするために右から左に数字が生成されます。 3からの選択は、4からの選択の前に展開されます。残念です。3から選択する前に4つの要素から選択したいからです。そうすることができないと、まずLehmerコードを計算して、実際の順列を計算するために項目の配列に適用します。

しかし、辞書編集の順序を気にしなければ、3つの要素から選択する前に、4つの要素から選択したいとふりかえることができます。次に、4の選択肢が unpack 言い換えれば、 unpack(n、[4、3])の代わりに unpack(n、[3、4])を使用します。このトリックでLehmerコードの次の桁を計算し、直ちにそれをリストに適用することができます。そして、それは 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));
}
25
追加された

あなたの順列を並べ替える方法(たとえば辞書順)に依存します。

それを行う1つの方法は、階乗番号システムです。これは、[0、 n!]とすべての順列。

そして、[0、n!]の任意の数iに対して、他の計算をせずにi番目の置換を計算することができます。

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

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

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

for more information on this decomposition, have a look at this thread : https://math.stackexchange.com/questions/53262/factorial-decomposition-of-integers

それが役に立てば幸い


この方法は、この rel="nofollow noreferrer">ウィキペディアの記事で述べられているように、レマーコード

nの順列を生成する明白な方法は、次の値を生成することです。   Lehmerコード(多分階乗数システムを使う   n!までの整数の表現)に変換し、それらを   対応する順列。しかし後者のステップでは、   簡単に、効率的に実装することは難しい   シーケンスからの選択とそれからの削除のそれぞれの操作は、   任意の位置で;の明確な表現の   シーケンスを配列またはリンクリストとして使用する場合は、両方とも必要です(異なる   理由)を変換するためのn2/4の操作について説明します。 nで   かなり小さいと思われる(特に、すべての   順列が必要です)、それはあまり問題ではありませんが、   ランダムと体系的な生成の両方があることが判明している   かなり優れた単純な選択肢。このため、   確かに可能かもしれないが、特別な   Lehmerからの変換を可能にするデータ構造   O(n log n)時間の順列へのコード。

ですから、n要素の集合に対して行うことができるのは、適合データ構造を持つO(n ln(n))です。

14
追加された
@SimoneMargaritelliあなたはどういう意味ですか?元の要素セットの1つのサブセットの置換が必要ですか?
追加された 著者 Ricky Bobby,
私はすでに階数系を知っていますが、出力順列のサイズが項目の最初のベクトルと同じでない実装を見つけることができません。
追加された 著者 Simone Margaritelli,
U = nなので、実際にはvEBツリーを使ってO(n lg lg U)を実行できます。下界は何ですか?
追加された 著者 dhruvbird,

これは、順列と順位の間を線形時間で変換するアルゴリズムです。しかし、それが使用するランキングは辞書編集的ではありません。それは不思議ですが一貫しています。私はランクから順列に変換する関数と逆関数を行う関数の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)です。

There's a nice, readable paper explaining why this works: Ranking & Unranking Permutations in Linear Time, by Myrvold & Ruskey, Information Processing Letters Volume 79, Issue 6, 30 September 2001, Pages 281–284.

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

7
追加された
配列のスプライス(または要素の削除)を行う必要がなく、ネストされたforループ+1が存在しないため、この解決策はおそらく最も高速です。
追加された 著者 James,

以下は、要素のリスト(下の例では最初の13文字)のために働く、Pythonの短くて非常に高速な(要素数の線形)解です。

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

注:関数がパラメータ elems を変更するため( letters のコピー) letters [:]

4
追加された
あなたのリストに重複した文字が含まれているとどうなりましたか?それは間違った結果を生み出している。
追加された 著者 Sonu Kumar,

すべての順列をメモリに格納する場合(たとえば配列内にある場合)、O(1)時間に1つずつ戻すことができるはずです。

これはすべての順列を保存しなければならないことを意味します。したがって、すべての順列を計算するのが非常に長い時間を要したり、それらを格納するのに非常に大きなスペースが必要な場合、これは解決策ではありません。

とにかく試してみるといいでしょう。それが大きすぎる/遅すぎると戻ってきます。純粋な人が仕事をするならば、 "賢い"解決策を探していることはありません。

1
追加された
あなたは正しいです、これはあなたが聞きたいものですか? :)それはちょうど誤解だった、それは簡単な男だ;)
追加された 著者 Simone Margaritelli,
私はそれが私が '...すべての順列を計算することなく... ...'と述べたのでちょっと明白だったと思う
追加された 著者 Simone Margaritelli,
私がこれを求めているのは、事前計算済みの順列ですでにテストしたからです...
追加された 著者 Simone Margaritelli,
耐えられない。事前計算された順列を使用するアルゴリズムは、順列を計算しません。 (私は質問と他の回答が役に立つと思ったので私はここにしかいません)。
追加された 著者 dansalmo,
彼が質問するつもりの質問に対する答えではなく、実際に質問した質問への答えをSimoneに与える+1。
追加された 著者 Patrick87,
申し訳ありませんが、私の精神的な力は今日私には失敗しているに違いありません。
追加された 著者 Chris Browne,
あなたは実際に "$ permutation_indexまですべての順列を計算せずに"と言っています。これは "順列をすべて計算しない"と同じではありません。それは私が誰かが文脈から自分自身を引用するのを見たのは初めてです!
追加された 著者 Chris Browne,
ああ、あなたは私が古い「落ち着かない」トリックを使って私を打ちのめした。私が今作成した回答は怒って聞こえて、私の評判に悪影響を及ぼすことがあります。とにかく、私はあまりにも、実際の進歩に興味を持って、とにかく "勝利議論"について騒がしいわけではありません。
追加された 著者 Chris Browne,
これは2年前の答えです。この質問に対する他の回答は完全に満足できるので、自分自身を編集する必要はありません(もしあれば、単に削除する傾向があります)。尋問されることへの私の態度は、記録のために、これらの日々のより多くの謙虚さと寛容になります。私は2011年10月27日に悪い一日を過ごしたばかりだったと思う。
追加された 著者 Chris Browne,

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

using System;
using System.Collections.Generic;

namespace WpfPermutations
{
    public class PermutationOuelletLexico3
    {
       //************************************************************************
        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; }

       //************************************************************************
        /// 
/// 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. ///
 
        /// 
        /// 
Value is not used as inpu, only as output. Re-use buffer in order to save memory
        /// 
        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;
            }
        }

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

            List valuesLeft = new List(_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
追加された
PHP - 日本のコミュニティ [ja]
PHP - 日本のコミュニティ [ja]
4 参加者の

このグループではPHPについて話します。 パートナー:kotaeta.com