web-dev-qa-db-ja.com

セットの順列の生成(最も効率的に)

次のように、セット(コレクション)のすべての順列を生成したいと思います。

Collection: 1, 2, 3
Permutations: {1, 2, 3}
              {1, 3, 2}
              {2, 1, 3}
              {2, 3, 1}
              {3, 1, 2}
              {3, 2, 1}

これは、一般に「方法」の問題ではなく、最も効率的な方法に関する問題です。また、すべての順列を生成してそれらを返したくありませんが、一度に1つの順列のみを生成し、必要な場合にのみ続行します(イテレータと同様に-これも試してみましたが、効率的)。

私は多くのアルゴリズムとアプローチをテストし、このコードを思い付きました。

public static bool NextPermutation<T>(T[] elements) where T : IComparable<T>
{
    // More efficient to have a variable instead of accessing a property
    var count = elements.Length;

    // Indicates whether this is the last lexicographic permutation
    var done = true;

    // Go through the array from last to first
    for (var i = count - 1; i > 0; i--)
    {
        var curr = elements[i];

        // Check if the current element is less than the one before it
        if (curr.CompareTo(elements[i - 1]) < 0)
        {
            continue;
        }

        // An element bigger than the one before it has been found,
        // so this isn't the last lexicographic permutation.
        done = false;

        // Save the previous (bigger) element in a variable for more efficiency.
        var prev = elements[i - 1];

        // Have a variable to hold the index of the element to swap
        // with the previous element (the to-swap element would be
        // the smallest element that comes after the previous element
        // and is bigger than the previous element), initializing it
        // as the current index of the current item (curr).
        var currIndex = i;

        // Go through the array from the element after the current one to last
        for (var j = i + 1; j < count; j++)
        {
            // Save into variable for more efficiency
            var tmp = elements[j];

            // Check if tmp suits the "next swap" conditions:
            // Smallest, but bigger than the "prev" element
            if (tmp.CompareTo(curr) < 0 && tmp.CompareTo(prev) > 0)
            {
                curr = tmp;
                currIndex = j;
            }
        }

        // Swap the "prev" with the new "curr" (the swap-with element)
        elements[currIndex] = prev;
        elements[i - 1] = curr;

        // Reverse the order of the tail, in order to reset it's lexicographic order
        for (var j = count - 1; j > i; j--, i++)
        {
            var tmp = elements[j];
            elements[j] = elements[i];
            elements[i] = tmp;
        }

        // Break since we have got the next permutation
        // The reason to have all the logic inside the loop is
        // to prevent the need of an extra variable indicating "i" when
        // the next needed swap is found (moving "i" outside the loop is a
        // bad practice, and isn't very readable, so I preferred not doing
        // that as well).
        break;
    }

    // Return whether this has been the last lexicographic permutation.
    return done;
}

使用法は、要素の配列を送信し、これが最後の辞書式順列であるかどうかを示すブール値を取得し、配列を次の順列に変更することです。

使用例:

var arr = new[] {1, 2, 3};

PrintArray(arr);

while (!NextPermutation(arr))
{
    PrintArray(arr);
}

問題は、コードの速度に満足していないことです。

サイズ11の配列のすべての順列の反復には、約4秒かかります。サイズ11のセットの可能な順列の量は11!これは約4,000万です。

論理的には、サイズ12の配列では、12!11! * 12、およびサイズ13の配列では、サイズ12でかかった時間の約13倍の時間がかかります。

したがって、サイズ12以上の配列を使用すると、すべての順列を実行するのに非常に長い時間がかかる方法を簡単に理解できます。

そして、私は何とかその時間を大幅に削減できる強い予感を持っています(C#以外の言語に切り替えることなく-コンパイラの最適化は実際にかなりうまく最適化されるため、アセンブリで手動で最適化できるとは思えません).

誰かがそれをより速く行う他の方法を知っていますか?現在のアルゴリズムを高速化する方法についてのアイデアはありますか?

それを行うために外部ライブラリまたはサービスを使用したくないことに注意してください-コード自体を持ちたいと思っています。

57
SimpleVar

更新2018-05-28:

ちょっと遅すぎる...

最近のテストによると(2018-05-22更新)

  • 最速は私のものですが、辞書式順序ではありません
  • 最速の辞書編集順序については、Sani Singh Huttunenのソリューションが最適です。

私のマシン(millisecs)でリリースされている10アイテム(10!)のパフォーマンステスト結果:

  • ウレット:29
  • SimpleVar:95
  • エレズ・ロビンソン:156
  • サニシンフットゥネン:37
  • Pengyang:45047

私のマシンでリリースされている13項目(13!)のパフォーマンステスト結果(秒):

  • ウレット:48.437
  • SimpleVar:159.869
  • エレズ・ロビンソン:327.781
  • サニシンフットゥネン:64.839

私のソリューションの利点:

  • ヒープのアルゴリズム(順列ごとの単一スワップ)
  • 乗算なし(Webで見られる一部の実装のように)
  • インラインスワップ
  • ジェネリック
  • 安全でないコードはありません
  • インプレース(非常に低いメモリ使用量)
  • モジュロなし(最初のビットの比較のみ)

ヒープのアルゴリズム :の私の実装

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;

namespace WpfPermutations
{
    /// <summary>
    /// EO: 2016-04-14
    /// Generator of all permutations of an array of anything.
    /// Base on Heap's Algorithm. See: https://en.wikipedia.org/wiki/Heap%27s_algorithm#cite_note-3
    /// </summary>
    public static class Permutations
    {
        /// <summary>
        /// Heap's algorithm to find all pmermutations. Non recursive, more efficient.
        /// </summary>
        /// <param name="items">Items to permute in each possible ways</param>
        /// <param name="funcExecuteAndTellIfShouldStop"></param>
        /// <returns>Return true if cancelled</returns> 
        public static bool ForAllPermutation<T>(T[] items, Func<T[], bool> funcExecuteAndTellIfShouldStop)
        {
            int countOfItem = items.Length;

            if (countOfItem <= 1)
            {
                return funcExecuteAndTellIfShouldStop(items);
            }

            var indexes = new int[countOfItem];
            for (int i = 0; i < countOfItem; i++)
            {
                indexes[i] = 0;
            }

            if (funcExecuteAndTellIfShouldStop(items))
            {
                return true;
            }

            for (int i = 1; i < countOfItem;)
            {
                if (indexes[i] < i)
                { // On the web there is an implementation with a multiplication which should be less efficient.
                    if ((i & 1) == 1) // if (i % 2 == 1)  ... more efficient ??? At least the same.
                    {
                        Swap(ref items[i], ref items[indexes[i]]);
                    }
                    else
                    {
                        Swap(ref items[i], ref items[0]);
                    }

                    if (funcExecuteAndTellIfShouldStop(items))
                    {
                        return true;
                    }

                    indexes[i]++;
                    i = 1;
                }
                else
                {
                    indexes[i++] = 0;
                }
            }

            return false;
        }

        /// <summary>
        /// This function is to show a linq way but is far less efficient
        /// From: StackOverflow user: Pengyang : http://stackoverflow.com/questions/756055/listing-all-permutations-of-a-string-integer
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });

            return GetPermutations(list, length - 1)
                .SelectMany(t => list.Where(e => !t.Contains(e)),
                    (t1, t2) => t1.Concat(new T[] { t2 }));
        }

        /// <summary>
        /// Swap 2 elements of same type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void Swap<T>(ref T a, ref T b)
        {
            T temp = a;
            a = b;
            b = temp;
        }

        /// <summary>
        /// Func to show how to call. It does a little test for an array of 4 items.
        /// </summary>
        public static void Test()
        {
            ForAllPermutation("123".ToCharArray(), (vals) =>
            {
                Console.WriteLine(String.Join("", vals));
                return false;
            });

            int[] values = new int[] { 0, 1, 2, 4 };

            Console.WriteLine("Ouellet heap's algorithm implementation");
            ForAllPermutation(values, (vals) =>
            {
                Console.WriteLine(String.Join("", vals));
                return false;
            });

            Console.WriteLine("Linq algorithm");
            foreach (var v in GetPermutations(values, values.Length))
            {
                Console.WriteLine(String.Join("", v));
            }

            // Performance Heap's against Linq version : huge differences
            int count = 0;

            values = new int[10];
            for (int n = 0; n < values.Length; n++)
            {
                values[n] = n;
            }

            Stopwatch stopWatch = new Stopwatch();

            ForAllPermutation(values, (vals) =>
            {
                foreach (var v in vals)
                {
                    count++;
                }
                return false;
            });

            stopWatch.Stop();
            Console.WriteLine($"Ouellet heap's algorithm implementation {count} items in {stopWatch.ElapsedMilliseconds} millisecs");

            count = 0;
            stopWatch.Reset();
            stopWatch.Start();

            foreach (var vals in GetPermutations(values, values.Length))
            {
                foreach (var v in vals)
                {
                    count++;
                }
            }

            stopWatch.Stop();
            Console.WriteLine($"Linq {count} items in {stopWatch.ElapsedMilliseconds} millisecs");
        }
    }
}

これは私のテストコードです:

Task.Run(() =>
            {

                int[] values = new int[12];
                for (int n = 0; n < values.Length; n++)
                {
                    values[n] = n;
                }

                // Eric Ouellet Algorithm
                int count = 0;
                var stopwatch = new Stopwatch();
                stopwatch.Reset();
                stopwatch.Start();
                Permutations.ForAllPermutation(values, (vals) =>
                {
                    foreach (var v in vals)
                    {
                        count++;
                    }
                    return false;
                });
                stopwatch.Stop();
                Console.WriteLine($"This {count} items in {stopwatch.ElapsedMilliseconds} millisecs");

                // Simple Plan Algorithm
                count = 0;
                stopwatch.Reset();
                stopwatch.Start();
                PermutationsSimpleVar permutations2 = new PermutationsSimpleVar();
                permutations2.Permutate(1, values.Length, (int[] vals) =>
                {
                    foreach (var v in vals)
                    {
                        count++;
                    }
                });
                stopwatch.Stop();
                Console.WriteLine($"Simple Plan {count} items in {stopwatch.ElapsedMilliseconds} millisecs");

                // ErezRobinson Algorithm
                count = 0;
                stopwatch.Reset();
                stopwatch.Start();
                foreach(var vals in PermutationsErezRobinson.QuickPerm(values))
                {
                    foreach (var v in vals)
                    {
                        count++;
                    }
                };
                stopwatch.Stop();
                Console.WriteLine($"Erez Robinson {count} items in {stopwatch.ElapsedMilliseconds} millisecs");
            });

使用例:

ForAllPermutation("123".ToCharArray(), (vals) =>
    {
        Console.WriteLine(String.Join("", vals));
        return false;
    });

int[] values = new int[] { 0, 1, 2, 4 };
ForAllPermutation(values, (vals) =>
        {
            Console.WriteLine(String.Join("", vals));
            return false;
        });
20
Eric Ouellet

これはあなたが探しているものかもしれません。

    private static bool NextPermutation(int[] numList)
    {
        /*
         Knuths
         1. Find the largest index j such that a[j] < a[j + 1]. If no such index exists, the permutation is the last permutation.
         2. Find the largest index l such that a[j] < a[l]. Since j + 1 is such an index, l is well defined and satisfies j < l.
         3. Swap a[j] with a[l].
         4. Reverse the sequence from a[j + 1] up to and including the final element a[n].

         */
        var largestIndex = -1;
        for (var i = numList.Length - 2; i >= 0; i--)
        {
            if (numList[i] < numList[i + 1]) {
                largestIndex = i;
                break;
            }
        }

        if (largestIndex < 0) return false;

        var largestIndex2 = -1;
        for (var i = numList.Length - 1 ; i >= 0; i--) {
            if (numList[largestIndex] < numList[i]) {
                largestIndex2 = i;
                break;
            }
        }

        var tmp = numList[largestIndex];
        numList[largestIndex] = numList[largestIndex2];
        numList[largestIndex2] = tmp;

        for (int i = largestIndex + 1, j = numList.Length - 1; i < j; i++, j--) {
            tmp = numList[i];
            numList[i] = numList[j];
            numList[j] = tmp;
        }

        return true;
    }
33

Cでそれを処理し、選択した言語に翻訳できる場合は、printが時間を支配するので、これより速く進むことはできません。

void perm(char* s, int n, int i){
  if (i >= n-1) print(s);
  else {
    perm(s, n, i+1);
    for (int j = i+1; j<n; j++){
      swap(s[i], s[j]);
      perm(s, n, i+1);
      swap(s[i], s[j]);
    }
  }
}

perm("ABC", 3, 0);
10
Mike Dunlavey

私が知っている最速の順列アルゴリズムは、QuickPermアルゴリズムです。
ここに実装があります。これはyield returnを使用しているため、必要に応じて一度に1つずつ繰り返すことができます。

コード:

public static IEnumerable<IEnumerable<T>> QuickPerm<T>(this IEnumerable<T> set)
    {
        int N = set.Count();
        int[] a = new int[N];
        int[] p = new int[N];

        var yieldRet = new T[N];

        List<T> list = new List<T>(set);

        int i, j, tmp; // Upper Index i; Lower Index j

        for (i = 0; i < N; i++)
        {
            // initialize arrays; a[N] can be any type
            a[i] = i + 1; // a[i] value is not revealed and can be arbitrary
            p[i] = 0; // p[i] == i controls iteration and index boundaries for i
        }
        yield return list;
        //display(a, 0, 0);   // remove comment to display array a[]
        i = 1; // setup first swap points to be 1 and 0 respectively (i & j)
        while (i < N)
        {
            if (p[i] < i)
            {
                j = i%2*p[i]; // IF i is odd then j = p[i] otherwise j = 0
                tmp = a[j]; // swap(a[j], a[i])
                a[j] = a[i];
                a[i] = tmp;

                //MAIN!

                for (int x = 0; x < N; x++)
                {
                    yieldRet[x] = list[a[x]-1];
                }
                yield return yieldRet;
                //display(a, j, i); // remove comment to display target array a[]

                // MAIN!

                p[i]++; // increase index "weight" for i by one
                i = 1; // reset index i to 1 (assumed)
            }
            else
            {
                // otherwise p[i] == i
                p[i] = 0; // reset p[i] to zero
                i++; // set new index value for i (increase by one)
            } // if (p[i] < i)
        } // while(i < N)
    }
8
Erez Robinson

これが私が終わった最速の実装です:

public class Permutations
{
    private readonly Mutex _mutex = new Mutex();

    private Action<int[]> _action;
    private Action<IntPtr> _actionUnsafe;
    private unsafe int* _arr;
    private IntPtr _arrIntPtr;
    private unsafe int* _last;
    private unsafe int* _lastPrev;
    private unsafe int* _lastPrevPrev;

    public int Size { get; private set; }

    public bool IsRunning()
    {
        return this._mutex.SafeWaitHandle.IsClosed;
    }

    public bool Permutate(int start, int count, Action<int[]> action, bool async = false)
    {
        return this.Permutate(start, count, action, null, async);
    }

    public bool Permutate(int start, int count, Action<IntPtr> actionUnsafe, bool async = false)
    {
        return this.Permutate(start, count, null, actionUnsafe, async);
    }

    private unsafe bool Permutate(int start, int count, Action<int[]> action, Action<IntPtr> actionUnsafe, bool async = false)
    {
        if (!this._mutex.WaitOne(0))
        {
            return false;
        }

        var x = (Action)(() =>
                             {
                                 this._actionUnsafe = actionUnsafe;
                                 this._action = action;

                                 this.Size = count;

                                 this._arr = (int*)Marshal.AllocHGlobal(count * sizeof(int));
                                 this._arrIntPtr = new IntPtr(this._arr);

                                 for (var i = 0; i < count - 3; i++)
                                 {
                                     this._arr[i] = start + i;
                                 }

                                 this._last = this._arr + count - 1;
                                 this._lastPrev = this._last - 1;
                                 this._lastPrevPrev = this._lastPrev - 1;

                                 *this._last = count - 1;
                                 *this._lastPrev = count - 2;
                                 *this._lastPrevPrev = count - 3;

                                 this.Permutate(count, this._arr);
                             });

        if (!async)
        {
            x();
        }
        else
        {
            new Thread(() => x()).Start();
        }

        return true;
    }

    private unsafe void Permutate(int size, int* start)
    {
        if (size == 3)
        {
            this.DoAction();
            Swap(this._last, this._lastPrev);
            this.DoAction();
            Swap(this._last, this._lastPrevPrev);
            this.DoAction();
            Swap(this._last, this._lastPrev);
            this.DoAction();
            Swap(this._last, this._lastPrevPrev);
            this.DoAction();
            Swap(this._last, this._lastPrev);
            this.DoAction();

            return;
        }

        var sizeDec = size - 1;
        var startNext = start + 1;
        var usedStarters = 0;

        for (var i = 0; i < sizeDec; i++)
        {
            this.Permutate(sizeDec, startNext);

            usedStarters |= 1 << *start;

            for (var j = startNext; j <= this._last; j++)
            {
                var mask = 1 << *j;

                if ((usedStarters & mask) != mask)
                {
                    Swap(start, j);
                    break;
                }
            }
        }

        this.Permutate(sizeDec, startNext);

        if (size == this.Size)
        {
            this._mutex.ReleaseMutex();
        }
    }

    private unsafe void DoAction()
    {
        if (this._action == null)
        {
            if (this._actionUnsafe != null)
            {
                this._actionUnsafe(this._arrIntPtr);
            }

            return;
        }

        var result = new int[this.Size];

        fixed (int* pt = result)
        {
            var limit = pt + this.Size;
            var resultPtr = pt;
            var arrayPtr = this._arr;

            while (resultPtr < limit)
            {
                *resultPtr = *arrayPtr;
                resultPtr++;
                arrayPtr++;
            }
        }

        this._action(result);
    }

    private static unsafe void Swap(int* a, int* b)
    {
        var tmp = *a;
        *a = *b;
        *b = tmp;
    }
}

使用およびテストのパフォーマンス:

var perms = new Permutations();

var sw1 = Stopwatch.StartNew();

perms.Permutate(0,
                11,
                (Action<int[]>)null); // Comment this line and...
                //PrintArr); // Uncomment this line, to print permutations

sw1.Stop();
Console.WriteLine(sw1.Elapsed);

印刷方法:

private static void PrintArr(int[] arr)
{
    Console.WriteLine(string.Join(",", arr));
}

さらに深く:

私はこれについて非常に長い間考えさえしなかったので、コードをあまり説明できませんが、ここに一般的な考えがあります:

  1. 順列は辞書式ではありません-これにより、順列間の操作を実質的に少なくすることができます。
  2. 実装は再帰的であり、「ビュー」サイズが3の場合、複雑なロジックをスキップし、6回のスワップを実行して6個の順列(または必要に応じてサブ順列)を取得します。
  3. 順列は辞書式順序ではないため、どの要素を現在の「ビュー」(サブ順列)の先頭に移動させるかをどのように決定できますか?現在のサブ順列再帰呼び出しで「スターター」として既に使用されている要素の記録を保持し、配列の末尾で使用されていない要素を単純に線形検索します。
  4. 実装は整数専用であるため、要素のジェネリックコレクションを置換するには、実際のコレクションではなくPermutationsクラスを使用してインデックスを置換するだけです。
  5. ミューテックスは、実行が非同期の場合に物事がねじ込まれないようにするためにあります(順番に並べ替えられた配列へのポインターを取得するUnsafeActionパラメーターを渡すことができます。その配列内の要素の順序を変更しないでください。 (ポインター)!必要に応じて、配列をtmp配列にコピーするか、それを処理するセーフアクションパラメーターを使用する必要があります-渡された配列は既にコピーされています)。

注:

この実装が実際にどれほど優れているかはわかりません。長い間触れていません。自分で他の実装とテストして比較し、フィードバックがあれば教えてください!

楽しい。

4
SimpleVar

2018-05-28の更新、新しいバージョン、最速...(マルチスレッド)

enter image description here

                            Time taken for fastest algorithms

必要性:Sani Singh Huttunen(最速のlexico)ソリューションと、インデックス作成をサポートする新しいOuelletLexico3

インデックス作成には2つの主な利点があります。

  • 誰でも順列を直接取得できます
  • マルチスレッド化が可能(最初の利点から派生)

記事: 順列:高速実装とマルチスレッド化を可能にする新しいインデックスアルゴリズム

私のマシン(6ハイパースレッドコア:12スレッド)Xeon E5-1660 0 @ 3.30Ghzでは、13を実行するために空のもので実行されるアルゴリズムをテストします!アイテム(ミリ秒単位の時間):

  • 53071:Ouellet(ヒープの実装)
  • 65366:サニシンフットゥネン(最速の語彙)
  • 11377:ミックスOuelletLexico3-サニシンフットゥネン

サイドノート:順列アクションのためにスレッド間で共有プロパティ/変数を使用すると、それらの使用が変更(読み取り/書き込み)の場合、パフォーマンスに大きな影響を与えます。そうすると、スレッド間で「 false sharing 」が生成されます。期待されるパフォーマンスは得られません。テスト中にこの動作が発生しました。私の経験では、置換の総数のグローバル変数を増やしようとすると問題が発生しました。

使用法:

PermutationMixOuelletSaniSinghHuttunen.ExecuteForEachPermutationMT(
  new int[] {1, 2, 3, 4}, 
  p => 
    { 
      Console.WriteLine($"Values: {p[0]}, {p[1]}, p[2]}, {p[3]}"); 
    });

コード:

using System;
using System.Runtime.CompilerServices;

namespace WpfPermutations
{
    public class Factorial
    {
        // ************************************************************************
        protected static long[] FactorialTable = new long[21];

        // ************************************************************************
        static Factorial()
        {
            FactorialTable[0] = 1; // To prevent divide by 0
            long f = 1;
            for (int i = 1; i <= 20; i++)
            {
                f = f * i;
                FactorialTable[i] = f;
            }
        }

        // ************************************************************************
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static long GetFactorial(int val) // a long can only support up to 20!
        {
            if (val > 20)
            {
                throw new OverflowException($"{nameof(Factorial)} only support a factorial value <= 20");
            }

            return FactorialTable[val];
        }

        // ************************************************************************

    }
}


namespace WpfPermutations
{
    public class PermutationSaniSinghHuttunen
    {
        public static bool NextPermutation(int[] numList)
        {
            /*
             Knuths
             1. Find the largest index j such that a[j] < a[j + 1]. If no such index exists, the permutation is the last permutation.
             2. Find the largest index l such that a[j] < a[l]. Since j + 1 is such an index, l is well defined and satisfies j < l.
             3. Swap a[j] with a[l].
             4. Reverse the sequence from a[j + 1] up to and including the final element a[n].

             */
            var largestIndex = -1;
            for (var i = numList.Length - 2; i >= 0; i--)
            {
                if (numList[i] < numList[i + 1])
                {
                    largestIndex = i;
                    break;
                }
            }

            if (largestIndex < 0) return false;

            var largestIndex2 = -1;
            for (var i = numList.Length - 1; i >= 0; i--)
            {
                if (numList[largestIndex] < numList[i])
                {
                    largestIndex2 = i;
                    break;
                }
            }

            var tmp = numList[largestIndex];
            numList[largestIndex] = numList[largestIndex2];
            numList[largestIndex2] = tmp;

            for (int i = largestIndex + 1, j = numList.Length - 1; i < j; i++, j--)
            {
                tmp = numList[i];
                numList[i] = numList[j];
                numList[j] = tmp;
            }

            return true;
        }
    }
}


using System;

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

        private bool[] _valueUsed;

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

        // ************************************************************************
        public PermutationOuelletLexico3(T[] sortedValues)
        {
            _sortedValues = sortedValues;
            Result = new T[_sortedValues.Length];
            _valueUsed = new bool[_sortedValues.Length];

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

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

        // ************************************************************************
        /// <summary>
        /// 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 GetSortedValuesFor(long sortIndex)
        {
            int size = _sortedValues.Length;

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

            if (sortIndex >= MaxIndex)
            {
                throw new ArgumentException("sortIndex should 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;
            }
        }

        // ************************************************************************
    }
}


using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace WpfPermutations
{
    public class PermutationMixOuelletSaniSinghHuttunen
    {
        // ************************************************************************
        private long _indexFirst;
        private long _indexLastExclusive;
        private int[] _sortedValues;

        // ************************************************************************
        public PermutationMixOuelletSaniSinghHuttunen(int[] sortedValues, long indexFirst = -1, long indexLastExclusive = -1)
        {
            if (indexFirst == -1)
            {
                indexFirst = 0;
            }

            if (indexLastExclusive == -1)
            {
                indexLastExclusive = Factorial.GetFactorial(sortedValues.Length);
            }

            if (indexFirst >= indexLastExclusive)
            {
                throw new ArgumentException($"{nameof(indexFirst)} should be less than {nameof(indexLastExclusive)}");
            }

            _indexFirst = indexFirst;
            _indexLastExclusive = indexLastExclusive;
            _sortedValues = sortedValues;
        }

        // ************************************************************************
        public void ExecuteForEachPermutation(Action<int[]> action)
        {
            //          Console.WriteLine($"Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} started: {_indexFirst} {_indexLastExclusive}");

            long index = _indexFirst;

            PermutationOuelletLexico3<int> permutationOuellet = new PermutationOuelletLexico3<int>(_sortedValues);

            permutationOuellet.GetSortedValuesFor(index);
            action(permutationOuellet.Result);
            index++;

            int[] values = permutationOuellet.Result;
            while (index < _indexLastExclusive)
            {
                PermutationSaniSinghHuttunen.NextPermutation(values);
                action(values);
                index++;
            }

            //          Console.WriteLine($"Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} ended: {DateTime.Now.ToString("yyyyMMdd_HHmmss_ffffff")}");
        }

        // ************************************************************************
        public static void ExecuteForEachPermutationMT(int[] sortedValues, Action<int[]> action)
        {
            int coreCount = Environment.ProcessorCount; // Hyper treading are taken into account (ex: on a 4 cores hyperthreaded = 8)
            long itemsFactorial = Factorial.GetFactorial(sortedValues.Length);
            long partCount = (long)Math.Ceiling((double)itemsFactorial / (double)coreCount);
            long startIndex = 0;

            var tasks = new List<Task>();

            for (int coreIndex = 0; coreIndex < coreCount; coreIndex++)
            {
                long stopIndex = Math.Min(startIndex + partCount, itemsFactorial);

                PermutationMixOuelletSaniSinghHuttunen mix = new PermutationMixOuelletSaniSinghHuttunen(sortedValues, startIndex, stopIndex);
                Task task = Task.Run(() => mix.ExecuteForEachPermutation(action));
                tasks.Add(task);

                if (stopIndex == itemsFactorial)
                {
                    break;
                }

                startIndex = startIndex + partCount;
            }

            Task.WaitAll(tasks.ToArray());
        }

        // ************************************************************************


    }
}
4
Eric Ouellet

これは、コレクションのすべての順列を反復処理し、評価関数を呼び出す汎用順列ファインダーです。評価関数がtrueを返した場合(探していた答えが見つかった場合)、置換Finderは処理を停止します。

public class PermutationFinder<T>
{
    private T[] items;
    private Predicate<T[]> SuccessFunc;
    private bool success = false;
    private int itemsCount;

    public void Evaluate(T[] items, Predicate<T[]> SuccessFunc)
    {
        this.items = items;
        this.SuccessFunc = SuccessFunc;
        this.itemsCount = items.Count();

        Recurse(0);
    }

    private void Recurse(int index)
    {
        T tmp;

        if (index == itemsCount)
            success = SuccessFunc(items);
        else
        {
            for (int i = index; i < itemsCount; i++)
            {
                tmp = items[index];
                items[index] = items[i];
                items[i] = tmp;

                Recurse(index + 1);

                if (success)
                    break;

                tmp = items[index];
                items[index] = items[i];
                items[i] = tmp;
            }
        }
    }
}

簡単な実装を次に示します。

class Program
{
    static void Main(string[] args)
    {
        new Program().Start();
    }

    void Start()
    {
        string[] items = new string[5];
        items[0] = "A";
        items[1] = "B";
        items[2] = "C";
        items[3] = "D";
        items[4] = "E";
        new PermutationFinder<string>().Evaluate(items, Evaluate);
        Console.ReadLine();
    }

    public bool Evaluate(string[] items)
    {
        Console.WriteLine(string.Format("{0},{1},{2},{3},{4}", items[0], items[1], items[2], items[3], items[4]));
        bool someCondition = false;

        if (someCondition)
            return true;  // Tell the permutation Finder to stop.

        return false;
    }
}
3
Sam

以下は、複雑なO(n * n!)を使用した再帰的な実装です。1 配列の要素の交換に基づいています。配列は1, 2, ..., nの値で初期化されます。

using System;

namespace Exercise
{
class Permutations
{
    static void Main(string[] args)
    {
        int setSize = 3;
        FindPermutations(setSize);
    }
    //-----------------------------------------------------------------------------
    /* Method: FindPermutations(n) */
    private static void FindPermutations(int n)
    {
        int[] arr = new int[n];
        for (int i = 0; i < n; i++)
        {
            arr[i] = i + 1;
        }
        int iEnd = arr.Length - 1;
        Permute(arr, iEnd);
    }
    //-----------------------------------------------------------------------------  
    /* Method: Permute(arr) */
    private static void Permute(int[] arr, int iEnd)
    {
        if (iEnd == 0)
        {
            PrintArray(arr);
            return;
        }

        Permute(arr, iEnd - 1);
        for (int i = 0; i < iEnd; i++)
        {
            swap(ref arr[i], ref arr[iEnd]);
            Permute(arr, iEnd - 1);
            swap(ref arr[i], ref arr[iEnd]);
        }
    }
}
}

各再帰ステップで、最後の要素をforループ内のローカル変数が指す現在の要素と交換し、次にforループのローカル変数をインクリメントすることにより、交換の一意性を示します。 forループの終了条件をデ​​クリメントします。これは、最初は配列内の要素の数に設定され、後者がゼロになったときに再帰を終了します。

ヘルパー関数は次のとおりです。

    //-----------------------------------------------------------------------------
    /*
        Method: PrintArray()

    */
    private static void PrintArray(int[] arr, string label = "")
    {
        Console.WriteLine(label);
        Console.Write("{");
        for (int i = 0; i < arr.Length; i++)
        {
            Console.Write(arr[i]);
            if (i < arr.Length - 1)
            {
                Console.Write(", ");
            }
        }
        Console.WriteLine("}");
    }
    //-----------------------------------------------------------------------------

    /*
        Method: swap(ref int a, ref int b)

    */
    private static void swap(ref int a, ref int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

1.印刷されるn要素のn!順列があります。

2
Ziezi

Steven Skienaの Algorithm Design Manual (第2版の14.4章)には、アルゴリズムの簡単な紹介と実装の調査があります。

SkienaはD. Knuthを参照しています。コンピュータープログラミングのアート、第4巻ファシクル2:すべてのタプルと順列の生成。アディソンウェスリー、2005年。

1
Colonel Panic

実際に大幅な改善が見られる場合、私は驚くでしょう。ある場合、C#には根本的な改善が必要です。さらに、順列で興味深いことを行うと、一般的に生成よりも多くの作業が必要になります。したがって、生成のコストは、全体的なスキームで取るに足らないものになるでしょう。

とはいえ、次のことを試してみることをお勧めします。既にイテレータを試しました。しかし、入力としてクロージャーを受け取り、見つかった置換ごとにそのクロージャーを呼び出す関数を試してみましたか? C#の内部メカニズムによっては、これはより高速になる場合があります。

同様に、特定の順列を反復するクロージャーを返す関数を試してみましたか?

どちらのアプローチでも、さまざまな最適化を試すことができます。たとえば、入力配列を並べ替えることができ、その後は常にその順序を知ることができます。たとえば、その要素が次のものよりも小さいかどうかを示すブールの配列を持つことができ、比較するのではなく、その配列を見てください。

1
btilly

この質問の著者がアルゴリズムについて尋ねていたとき:

[...]一度に1つの順列を生成し、必要な場合にのみ続行する

Steinhaus–Johnson–Trotterアルゴリズムを検討することをお勧めします。

WikipediaのSteinhaus–Johnson–Trotterアルゴリズム

美しく説明 ここ

1
misiek

Knuthのアルゴリズムよりもわずかに速いアルゴリズムを作成しました。

11要素:

鉱山:0.39秒

クヌース:0.624秒

13要素:

鉱山:56.615秒

クヌース:98.681秒

Javaのコードは次のとおりです。

public static void main(String[] args)
{
    int n=11;
    int a,b,c,i,tmp;
    int end=(int)Math.floor(n/2);
    int[][] pos = new int[end+1][2];
    int[] perm = new int[n];

    for(i=0;i<n;i++) perm[i]=i;

    while(true)
    {
        //this is where you can use the permutations (perm)
        i=0;
        c=n;

        while(pos[i][1]==c-2 && pos[i][0]==c-1)
        {
            pos[i][0]=0;
            pos[i][1]=0;
            i++;
            c-=2;
        }

        if(i==end) System.exit(0);

        a=(pos[i][0]+1)%c+i;
        b=pos[i][0]+i;

        tmp=perm[b];
        perm[b]=perm[a];
        perm[a]=tmp;

        if(pos[i][0]==c-1)
        {
            pos[i][0]=0;
            pos[i][1]++;
        }
        else
        {
            pos[i][0]++;
        }
    }
}

問題は、私のアルゴリズムが奇数の要素に対してのみ機能することです。すぐにこのコードを書いたので、より良いパフォーマンスを得るためのアイデアを実装するより良い方法があると確信していますが、今それを最適化して問題を解決するために今すぐ取り組む時間はありません要素は偶数です。

これはすべての順列に対して1回のスワップであり、どの要素をスワップするかを知るための非常に簡単な方法を使用します。

私は私のブログでコードの背後にあるメソッドの説明を書きました: http://antoinecomeau.blogspot.ca/2015/01/fast-generation-of-all-permutations.html

1
Antoine Comeau

午前1時で、私はテレビを見ていて、この同じ質問を考えていましたが、文字列の値を使用していました。

Wordが与えられた場合、すべての順列を見つけます。これを簡単に変更して、配列やセットなどを処理できます。

それを解決するために少しかかりましたが、私が思いついた解決策はこれでした:

string Word = "abcd";

List<string> combinations = new List<string>();

for(int i=0; i<Word.Length; i++)
{
    for (int j = 0; j < Word.Length; j++)
    {
        if (i < j)
            combinations.Add(Word[i] + Word.Substring(j) + Word.Substring(0, i) + Word.Substring(i + 1, j - (i + 1)));
        else if (i > j)
        {
            if(i== Word.Length -1)
                combinations.Add(Word[i] + Word.Substring(0, i));
            else
                combinations.Add(Word[i] + Word.Substring(0, i) + Word.Substring(i + 1));
        }
    }
}

上記と同じコードですが、コメントがあります

string Word = "abcd";

List<string> combinations = new List<string>();

//i is the first letter of the new Word combination
for(int i=0; i<Word.Length; i++)
{
    for (int j = 0; j < Word.Length; j++)
    {
        //add the first letter of the Word, j is past i so we can get all the letters from j to the end
        //then add all the letters from the front to i, then skip over i (since we already added that as the beginning of the Word)
        //and get the remaining letters from i+1 to right before j.
        if (i < j)
            combinations.Add(Word[i] + Word.Substring(j) + Word.Substring(0, i) + Word.Substring(i + 1, j - (i + 1)));
        else if (i > j)
        {
            //if we're at the very last Word no need to get the letters after i
            if(i== Word.Length -1)
                combinations.Add(Word[i] + Word.Substring(0, i));
            //add i as the first letter of the Word, then get all the letters up to i, skip i, and then add all the lettes after i
            else
                combinations.Add(Word[i] + Word.Substring(0, i) + Word.Substring(i + 1));

        }
    }
}
0
Raymond
// Permutations are the different ordered arrangements of an n-element
// array. An n-element array has exactly n! full-length permutations.

// This iterator object allows to iterate all full length permutations
// one by one of an array of n distinct elements.

// The iterator changes the given array in-place.

// Permutations('ABCD') => ABCD  DBAC  ACDB  DCBA
//                         BACD  BDAC  CADB  CDBA
//                         CABD  ADBC  DACB  BDCA
//                         ACBD  DABC  ADCB  DBCA
//                         BCAD  BADC  CDAB  CBDA
//                         CBAD  ABDC  DCAB  BCDA

// count of permutations = n!

// Heap's algorithm (Single swap per permutation)
// http://www.quickperm.org/quickperm.php
// https://stackoverflow.com/a/36634935/4208440
// https://en.wikipedia.org/wiki/Heap%27s_algorithm


// My implementation of Heap's algorithm:

template<typename T>
class PermutationsIterator
{
        int b, e, n;
        int c[32];  /* control array: mixed radix number in rising factorial base.
                    the i-th digit has base i, which means that the digit must be
                    strictly less than i. The first digit is always 0,  the second
                    can be 0 or 1, the third 0, 1 or 2, and so on.
                    ArrayResize isn't strictly necessary, int c[32] would suffice
                    for most practical purposes. Also, it is much faster */

public:
        PermutationsIterator(T &arr[], int firstIndex, int lastIndex)
        {
                this.b = firstIndex;  // v.begin()
                this.e = lastIndex;   // v.end()
                this.n = e - b + 1;

                ArrayInitialize(c, 0);
        }

        // Rearranges the input array into the next permutation and returns true.
        // When there are no more permutations left, the function returns false.

        bool next(T &arr[])
        {
                // find index to update
                int i = 1;

                // reset all the previous indices that reached the maximum possible values
                while (c[i] == i)
                {
                        c[i] = 0;
                        ++i;
                }

                // no more permutations left
                if (i == n)
                        return false;

                // generate next permutation
                int j = (i & 1) == 1 ? c[i] : 0;    // IF i is odd then j = c[i] otherwise j = 0.
                swap(arr[b + j], arr[b + i]);       // generate a new permutation from previous permutation using a single swap

                // Increment that index
                ++c[i];
                return true;
        }

};
0
Amr Ali

ロゼッタコードでこのアルゴリズムを見つけましたが、実際に試した最速のアルゴリズムです。 http://rosettacode.org/wiki/Permutations#C

/* Boothroyd method; exactly N! swaps, about as fast as it gets */
void boothroyd(int *x, int n, int nn, int callback(int *, int))
{
        int c = 0, i, t;
        while (1) {
                if (n > 2) boothroyd(x, n - 1, nn, callback);
                if (c >= n - 1) return;
 
                i = (n & 1) ? 0 : c;
                c++;
                t = x[n - 1], x[n - 1] = x[i], x[i] = t;
                if (callback) callback(x, nn);
        }
}
 
/* entry for Boothroyd method */
void perm2(int *x, int n, int callback(int*, int))
{
        if (callback) callback(x, n);
        boothroyd(x, n, n, callback);
}
 
0
Amr Ali
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
/**
 * http://marknelson.us/2002/03/01/next-permutation/
 * Rearranges the elements into the lexicographically next greater permutation and returns true.
 * When there are no more greater permutations left, the function eventually returns false.
 */

// next lexicographical permutation

template <typename T>
bool next_permutation(T &arr[], int firstIndex, int lastIndex)
{
    int i = lastIndex;
    while (i > firstIndex)
    {
        int ii = i--;
        T curr = arr[i];
        if (curr < arr[ii])
        {
            int j = lastIndex;
            while (arr[j] <= curr) j--;
            Swap(arr[i], arr[j]);
            while (ii < lastIndex)
                Swap(arr[ii++], arr[lastIndex--]);
            return true;
        }
    }
    return false;
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
/**
 * Swaps two variables or two array elements.
 * using references/pointers to speed up swapping.
 */
template<typename T>
void Swap(T &var1, T &var2)
{
    T temp;
    temp = var1;
    var1 = var2;
    var2 = temp;
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
// driver program to test above function
#define N 3

void OnStart()
{
    int i, x[N];
    for (i = 0; i < N; i++) x[i] = i + 1;

    printf("The %i! possible permutations with %i elements:", N, N);

    do
    {
        printf("%s", ArrayToString(x));

    } while (next_permutation(x, 0, N - 1));

}

// Output:
// The 3! possible permutations with 3 elements:
// "1,2,3"
// "1,3,2"
// "2,1,3"
// "2,3,1"
// "3,1,2"
// "3,2,1"
0
Amr Ali