生成集合的排列(最有效)

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

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!11! * 12,而如果数组的大小是13,那么花费的时间就会比大小为12的时间多出13倍,以此类推。

所以你可以很容易地理解,当数组的大小为12或更大时,要完成所有的排列真的需要很长的时间。

而且我有一个强烈的预感,我可以在某种程度上大大缩短这个时间(不用切换到 C # 以外的语言——因为编译器最佳化确实可以很好地优化,而且我怀疑我可以在 Assembly 中手动优化得那么好)。

有人知道其他更快完成的方法吗? 你知道如何让现有的算法更快吗?

请注意,我不想使用外部库或服务来做到这一点-我希望有代码本身,我希望它尽可能高效。

63256 次浏览

这可能就是你要找的。

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

据我所知,最快的排列算法是 QuickPerm 算法。
下面是实现,它使用屈服返回值,这样您就可以像需要的那样一次迭代一个。 < BR >

密码:

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

在 Steven Skiena 的 < em > 算法设计手册 (第二版第14.4章)中有一个关于算法和实现调查的简单介绍

Skiena 引用了 D. Knuth。计算机编程艺术,卷4分册2: 生成所有元组和排列。Addison Wesley 2005年。

如果真的有数量级的改进,我会感到惊讶。如果有,那么 C # 需要根本的改进。此外,用你的排列做任何有趣的事情通常比生成它需要更多的工作。所以发电的成本在整个计划中是微不足道的。

也就是说,我建议尝试以下方法。您已经尝试过迭代器。但是,您是否尝试过让一个函数接受一个闭包作为输入,然后对找到的每个排列调用该闭包?根据 C # 的内部机制,这可能更快。

类似地,您是否尝试过使用一个函数来返回一个闭包,该闭包将在特定的排列上进行迭代?

无论采用哪种方法,您都可以尝试许多微型优化。例如,您可以对输入数组进行排序,然后您总是知道它的顺序。例如,您可以有一个指示该元素是否小于下一个元素的布尔数组,您可以只查看该数组,而不进行比较。

好吧,如果你能用 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);

下面是一个通用的排列查找器,它将遍历集合的每个排列并调用一个计算函数。如果计算函数返回 true (它找到了它正在寻找的答案) ,排列查找程序将停止处理。

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

下面是我最终得到的最快的实现:

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. 因为这些排列不是按照字典顺序排列的,我如何决定将哪个元素带到当前“视图”(子排列)的开始?我保存了当前子排列递归调用中已经用作“ start”的元素的记录,并简单地线性搜索数组尾部没有使用的元素。
  4. 该实现仅用于整数,因此要置换通用元素集合,只需使用 Permutations 类置换索引,而不是实际的集合。
  5. Mutex 的存在只是为了确保当执行是异步的时候不会出错(注意,您可以传递一个 UnsafeAction 参数,该参数将依次获得一个指向置换数组的指针。不能更改该数组(指针)中元素的顺序!如果你想,你应该把数组复制到 tmp 数组,或者使用 safe action 参数来处理这个问题——传递的数组已经是一个拷贝了)。

注:

我不知道这个实现到底有多好——我已经很久没有碰过它了。 测试和比较您自己的其他实现,并让我知道如果您有任何反馈!

好好享受吧。

我创建了一个比 Knuth 快一点的算法:

11个要素:

我的是0.39秒

Knuth 的成绩是0.624秒

13个要素:

我的是56.615秒

Knuth 的成绩是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]++;
}
}
}

问题是我的算法只适用于奇数个元素。我很快写了这段代码,所以我很确定有更好的方法来实现我的想法,以获得更好的性能,但我现在真的没有时间去优化它,并解决这个问题时,元素的数量是偶数。

它是每个排列的一个交换,它使用一种非常简单的方法来知道要交换哪些元素。

我在我的博客上写了一篇关于代码背后的方法的解释: http://antoinecomeau.blogspot.ca/2015/01/fast-generation-of-all-permutations.html

如果你只是想计算可能的排列数,你可以避免上面所有的艰苦工作,使用这样的东西(在 c # 中人为设计的) :

public static class ContrivedUtils
{
public static Int64 Permutations(char[] array)
{
if (null == array || array.Length == 0) return 0;


Int64 permutations = array.Length;


for (var pos = permutations; pos > 1; pos--)
permutations *= pos - 1;


return permutations;
}
}

你可以这么说:

var permutations = ContrivedUtils.Permutations("1234".ToCharArray());
// output is: 24
var permutations = ContrivedUtils.Permutations("123456789".ToCharArray());
// output is: 362880

更新2018-05-28:

有点太晚了。

根据最近的测试(更新于2018-05-22)

  • 最快的是我的,但不是按字典顺序
  • 对于最快的字典顺序,Sani Singh Huttunen 解决方案似乎是一条出路。

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

  • Ouellet: 29
  • SimpleVar: 95
  • Erez Robinson: 156
  • Sani Singh Huttunen: 37分
  • 彭阳: 45047

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

  • Ouellet: 48.437
  • SimpleVar: 159.869
  • Erez Robinson: 327.781
  • Sani Singh Huttunen: 64.839

我的解决方案的优点是:

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

我对 堆的算法的实现:

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

// Unecessary. Thanks to NetManage for the advise
// 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;
});

下面是一个复杂度为 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循环的终止条件来表示交换的唯一性,这个条件最初设置为数组中元素的数目,当后者变为零时,我们终止递归。

下面是 helper 函数:

    //-----------------------------------------------------------------------------
/*
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!排列。

当这个问题的作者问到一个算法时:

[ ... ]一次产生一个单一的排列,只有在必要时才继续

我建议考虑 Steinhaus-Johnson-Trotter 算法。

维基百科上的 Steinhaus-Johnson-Trotter 算法

很好地解释了 给你

现在是凌晨1点,我正在看电视,我想到了同样的问题,但是是字符串值。

给定一个单词,找到所有的排列。你可以很容易地修改它来处理数组、集合等。

我花了点时间才想出来,但我想到的解决办法是这样的:

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


}
}
}

我在罗塞塔代码中找到了这个算法,它是我试过的速度最快的一个

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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
/**
* 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"

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


};

更新2018-05-28,新版本,最快... (多线程)

enter image description here

                            Time taken for fastest algorithms

需要: Sani Singh Huttunen (最快的 lexico)解决方案和我的新 OuelletLexico3,它支持索引

索引有两个主要优点:

  • 允许任何人直接排列
  • 允许多线程(从第一个优点派生)

文章: 排列: 快速实现和允许多线程的新索引算法

在我的机器上(6个超线程核: 12个线程) ,至强 E5-16600@3.30 Ghz,测试运行的算法与空的东西做13!项目(以毫克为单位的时间) :

  • 53071: Ouellet (堆的实现)
  • 65366: Sani Singh Huttunen (最快词汇)
  • 11377: Mix OuelletLexico3-Sani Singh Huttunen

附注: 在线程之间使用共享属性/变量进行排列操作,如果使用这些属性/变量进行修改(读/写) ,则会对性能产生很大影响。这样做将在线程之间生成“ 虚假分享”。你不会得到预期的表现。我在测试的时候有这种行为。我的经验表明问题时,我试图增加全局变量的总计数的排列。

用法:

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


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




}
}

简单的 C # 递归解决方案通过交换,对于初始调用,索引必须为0

    static public void Permute<T>(List<T> input, List<List<T>> permutations, int index)
{
if (index == input.Count - 1)
{
permutations.Add(new List<T>(input));
return;
}


Permute(input, permutations, index + 1);


for (int i = index+1 ; i < input.Count; i++)
{
//swap
T temp = input[index];
input[index] = input[i];
input[i] = temp;


Permute(input, permutations, index + 1);


//swap back
temp = input[index];
input[index] = input[i];
input[i] = temp;
}
}