Создание перестановок набора (наиболее эффективно)
Я хотел бы сгенерировать все перестановки набора (коллекции), например:
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 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!
, что составляет около 40 миллионов.
Логически, с массивом размера 12 потребуется примерно в 12 раз больше времени, так как 12!
- 11! * 12
, а с массивом размером 13 потребуется примерно в 13 раз больше времени, чем время, затрачиваемое на размер 12 и т.д.
Итак, вы можете легко понять, как с помощью массива размером от 12 и более требуется очень много времени, чтобы пройти через все перестановки.
И у меня есть сильная догадка, что я могу как-то сократить время (не переключаясь на язык, отличный от С#), потому что оптимизация компилятора действительно оптимизирует довольно красиво, и я сомневаюсь, что я мог бы оптимизировать как хорошо, вручную, в Монтаж).
Кто-нибудь знает какой-либо другой способ сделать это быстрее?
Есть ли у вас какие-либо идеи относительно того, как быстрее выполнять текущий алгоритм?
Обратите внимание, что я не хочу использовать внешнюю библиотеку или службу для этого - я хочу иметь сам код, и я хочу, чтобы он был настолько эффективным, насколько это было возможно по-человечески.
Спасибо, что прочитал все!:)
Ответы
Ответ 1
Вот самая быстрая реализация, с которой я закончил:
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));
}
Перейти глубже:
Я даже не думал об этом в течение очень долгого времени, поэтому я могу только объяснить свой код так много, но здесь общая идея:
- Перестановки не лексикографические - это позволяет мне практически выполнять меньше операций между перестановками.
- Реализация рекурсивна, а когда размер представления равен 3, он пропускает сложную логику и просто выполняет 6 свопов, чтобы получить 6 перестановок (или подперестановок, если хотите).
- Поскольку перестановки не находятся в лексикографическом порядке, как я могу решить, какой элемент нужно довести до начала текущего "представления" (субперестановка)? Я сохраняю запись элементов, которые уже использовались в качестве "стартеров" в текущем рекурсивном вызове с перестановкой и просто выполняются линейно для одного, который не использовался в хвосте моего массива.
- Реализация предназначена только для целых чисел, поэтому для перестановки над общим набором элементов вы просто используете класс Permutations для перестановки индексов вместо вашей фактической коллекции.
- Мьютекс предназначен только для обеспечения того, чтобы все не зависало, когда выполнение является асинхронным (обратите внимание, что вы можете передать параметр UnsafeAction, который, в свою очередь, получит указатель на перенастроенный массив. Вы не должны изменять порядок элементов в этом массиве (указатель)! Если вы хотите, вы должны скопировать массив в массив tmp или просто использовать безопасный параметр действия, который позаботится об этом для вас - переданный массив уже является копией).
Примечание:
Я понятия не имею, насколько хороша эта реализация на самом деле - я так долго ее не трогал.
Испытайте и сравните с другими реализациями самостоятельно, и дайте мне знать, если у вас есть обратная связь!
Enjoy.
Ответ 2
Это может быть то, что вы ищете.
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;
}
Ответ 3
Хорошо, если вы можете справиться с этим на 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);
Ответ 4
Самый быстрый алгоритм перестановки, о котором я знаю, является алгоритмом QuickPerm.
Вот реализация, она использует возврат доходности, чтобы вы могли выполнять итерацию по мере необходимости.
Код:
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)
}
Ответ 5
Немного поздно...
Согласно моим тестам, моя реализация алгоритм кучи, как представляется, дает максимальную производительность...
Результаты теста производительности для 12 элементов (12!) в выпуске на моей машине:
- 1453051904 items in 10728 millisecs ==> My implementation of Heap (code included)
- 1453051904 items in 18053 millisecs ==> Simple Plan
- 1453051904 items in 85355 millisecs ==> Erez Robinson
Преимущества:
- Алгоритм кучи (Single swap на перестановку)
- Нет умножения (например, некоторые реализации, видимые в Интернете)
- Встроенный обмен
- Generic
- Небезопасный код
- На месте (очень низкая память)
- Нет modulo (только сравнение первого бит)
Моя реализация алгоритм кучи:
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 Algorithm. See: https://en.wikipedia.org/wiki/Heap%27s_algorithm#cite_note-3
/// </summary>
public static class Permutations
{
/// <summary>
/// Heap 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 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 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 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;
});
Ответ 6
Вот общий поиск перестановок, который будет перебирать каждую перестановку коллекции и вызывать функцию evalution. Если функция evalution возвращает 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;
}
}
Ответ 7
Как есть много ответов, я сделал несколько сводок. Для 11 предметов:
OP original (NextPermutation) 363ms
Sani Huttunen (NextPermutation) 334ms
Mike Dunlavey (perm) 433ms
Erez Robinson (QuickPerm) 210ms
Sam (PermutationFinder) 608ms
Я отклонил ответ SimpleVars, поскольку он не работает корректно и небезопасен. Это было быстрее всего, когда не было никаких действий, но с действием на перестановку скорость значительно уменьшилась. Я оптимизировал ответ @ErezRobinson немного, чтобы увидеть реальную производительность алгоритма. Это самый быстрый, слишком грустный, я понятия не имею, как это работает. Btw, когда я переместил суммирование (более поздним) в процедуру, чтобы избежать вызова действия, он достиг 160 мс.
public void QuickPermErez<T>(IEnumerable<T> set, Action<T[]> action)
{
T[] arr = set.ToArray();
int N = arr.Length;
int[] p = new int[N];
int i, j;// Upper Index i; Lower Index j
T tmp;
action(arr);
i = 1; // setup first swap points to be 1 and 0 respectively (i & j)
while (i < N)
{
if (p[i] < i)
{
j = (i & 1) * p[i]; // IF i is odd then j = p[i] otherwise j = 0
tmp = arr[j]; // swap(arr[j], arr[i])
arr[j] = arr[i];
arr[i] = tmp;
action(arr);
p[i]++;
i = 1;
}
else // p[i] == i
{
p[i] = 0; // reset p[i] to zero
i++;
}
}
}
Я суммировал первый элемент в массиве каждой перестановки, чтобы выполнить базовый тест и сделать его по крайней мере немного реальным сценарием. Я изменил перм, чтобы его можно было тестировать таким образом, но он был действительно косметическим.
void perm(int[] s, int n, int i, Action<int[]> ev)
{
if (i >= n - 1) ev(s);
else {
perm(s, n, i + 1, ev);
for (int j = i + 1; j < n; j++)
{
//swap(s[i], s[j]);
int tmp = s[i];
s[i] = s[j];
s[j] = tmp;
perm(s, n, i + 1, ev);
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
}
И для полноты кода тестирования:
int[] arr;
int cnt;
Stopwatch st = new Stopwatch();
int N = 11;
arr = Enumerable.Range(1, N).ToArray();
cnt = 0;
st.Restart();
do
{
cnt += arr[0];
} while (!NextPermutationOP(arr));
Console.WriteLine("OP: " + cnt + ", " + st.ElapsedMilliseconds.ToString());
arr = Enumerable.Range(1, N).ToArray();
cnt = 0;
st.Restart();
do
{
cnt += arr[0];
} while (NextPermutationSani(arr));
Console.WriteLine("Sani: " + cnt + ", " + st.ElapsedMilliseconds.ToString());
arr = Enumerable.Range(1, N).ToArray();
cnt = 0;
st.Restart();
new PermutationFinder<int>().Evaluate(arr, (x) =>
{
cnt += x[0];
return false;
});
Console.WriteLine("Sam: " + cnt + ", " + st.ElapsedMilliseconds.ToString());
arr = Enumerable.Range(1, N).ToArray();
cnt = 0;
st.Restart();
perm(arr, N, 0, (x) => cnt += x[0]);
Console.WriteLine("Mike: " + cnt + ", " + st.ElapsedMilliseconds.ToString());
arr = Enumerable.Range(1, N).ToArray();
cnt = 0;
st.Restart();
QuickPermErez(arr, (x) => cnt += x[0]);
Console.WriteLine("Erez: " + cnt + ", " + st.ElapsedMilliseconds.ToString());
Ответ 8
Вот рекурсивная реализация со сложностью 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
для печати.
Ответ 9
Там доступно введение в алгоритмы и обзор реализаций в Steven Skiena Руководство по разработке алгоритмов (глава 14.4 во втором издании)
Локена ссылается на Д. Кнута. Искусство компьютерного программирования, том 4. Fascicle 2: Генерация всех кортежей и перестановок. Addison Wesley, 2005.
Ответ 10
Я был бы удивлен, если бы действительно были исправления порядка. Если есть, то С# нуждается в фундаментальном улучшении. Кроме того, делать что-либо интересное с вашей перестановкой, как правило, требует больше работы, чем ее генерация. Таким образом, стоимость генерации будет незначительной в общей схеме вещей.
Тем не менее, я предлагаю попробовать следующее. Вы уже пробовали итераторы. Но попробовали ли вы функцию, которая принимает замыкание в качестве входных данных, а затем вызывает это замыкание для каждой найденной перестановки? В зависимости от внутренней механики С# это может быть быстрее.
Аналогично, попробовали ли вы функцию, которая возвращает замыкание, которое будет перебирать определенную перестановку?
При любом подходе существует ряд микро-оптимизаций, с которыми вы можете экспериментировать. Например, вы можете отсортировать свой входной массив и после этого вы всегда знаете, в каком порядке он находится. Например, у вас может быть массив bools, указывающий, является ли этот элемент меньше следующего, и вместо сравнения вы можете просто посмотрите на этот массив.
Ответ 11
Я создал алгоритм немного быстрее, чем Knuth one:
11 элементов:
мину: 0,39 секунды
Кнут: 0,624 секунды
13 элементов:
mine: 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]++;
}
}
}
Проблема заключается в том, что мой алгоритм работает только для нечетных чисел элементов. Я быстро написал этот код, поэтому я уверен, что есть лучший способ реализовать мою идею, чтобы получить лучшую производительность, но сейчас у меня нет времени для работы над этим, чтобы оптимизировать ее и решить проблему, когда число элементы четные.
Это одна своп для каждой перестановки, и она использует действительно простой способ узнать, какие элементы нужно менять.
Я написал объяснение метода кода в своем блоге: http://antoinecomeau.blogspot.ca/2015/01/fast-generation-of-all-permutations.html
Ответ 12
Как автор этого вопроса задавал вопрос об алгоритме:
[...], создавая единую перестановку за раз и продолжая только при необходимости
Я бы предложил рассмотреть алгоритм Steinhaus-Johnson-Trotter.
алгоритм Steinhaus-Johnson-Trotter в Википедии
Красиво объяснено здесь
Ответ 13
Это 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));
}
}
}
Ответ 14
Если вы просто хотите рассчитать количество возможных перестановок, вы можете избежать этой тяжелой работы выше и использовать что-то вроде этого (изобретенное в С#):
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