生成一组排列(最有效)

我想生成一个集合(集合)的所有排列,如下所示:

Collection: 1, 2, 3
Permutations: {1, 2, 3}
              {1, 3, 2}
              {2, 1, 3}
              {2, 3, 1}
              {3, 1, 2}
              {3, 2, 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! 近4000万。

从逻辑上讲,对于大小为12的阵列,从12开始将花费大约12倍的时间12!11! * 12 11! * 12 ,并且对于大小为13的数组,它将花费比大小为12的时间多13倍的时间,等等。

所以你可以很容易地理解如何使用12或更大的数组,实现所有排列真的需要很长时间。

我有一种强烈的预感,我可以通过很多方式减少这些时间(不用切换到C#以外的语言 - 因为编译器优化确实非常好地进行了优化,我怀疑我可以在Assembly中手动优化)。

有没有人知道任何其他方式可以更快地完成这项工作? 你对如何使当前的算法更快有什么想法吗?

请注意,我不想使用外部库或服务来实现这一点 - 我希望自己拥有代码,并且希望它能够像人类一样高效。


有点太晚了...

根据我的测试,我对堆的算法的实现似乎给了最快的性能......

在我的机器上发布的12个项目(12!)的性能测试结果:

 - 1453051904 items in 10728 millisecs ==> My implementation of Heap (code included)
 - 1453051904 items in 18053 millisecs ==> SimpleVar
 - 1453051904 items in 85355 millisecs ==> Erez Robinson

优点:

  • 堆的算法(每个置换单个交换)
  • 没有乘法(就像在网上看到的一些实现)
  • 内联交换
  • 通用
  • 没有不安全的代码
  • 到位(内存使用量非常低)
  • 不模(仅第一位比较)
  • 我的堆实现算法:

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

    这可能是你正在寻找的。

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

    那么,如果你可以用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);
    
    链接地址: http://www.djcxy.com/p/24317.html

    上一篇: Generating permutations of a set (most efficiently)

    下一篇: Generating cyclic permutations / reduced Latin Squares in Python