Перелік усіх перестановок рядка / цілого числа


159

Загальне завдання в програмуванні інтерв'ю (хоча не з мого досвіду інтерв'ю) - це взяти рядок або ціле число і перерахувати всі можливі перестановки.

Чи є приклад того, як це робиться, і логіка вирішення такої проблеми?

Я бачив кілька фрагментів коду, але вони не були добре прокоментовані / пояснені, і тому важко дотримуватися.


Відповіді:


152

Перш за все: це пахне рекурсією звичайно!

Оскільки ви також хотіли знати принцип, я зробив все можливе, щоб пояснити це людською мовою. Я думаю, що більшість випадків рекурсія дуже проста. Вам потрібно лише зрозуміти два кроки:

  1. Перший крок
  2. Усі інші кроки (всі з тією ж логікою)

У людській мові :

Якщо коротко:
1. Перестановка 1 елемента - це один елемент.
2. Перестановка набору елементів - це перелік кожного з елементів, з'єднаний з кожною перестановкою інших елементів.

Приклад:

Якщо у набору є лише один елемент ->
поверніть його.
perm (a) -> a

Якщо набір має два символи: для кожного елемента в ньому: поверніть елемент із додаванням перестановки решти елементів, як:

perm (ab) ->

a + perm (b) -> ab

b + perm (a) -> ba

Далі: для кожного символу в наборі: поверніть символ, об'єднаний з перетворенням> решти набору

perm (abc) ->

a + perm (bc) -> abc , acb

b + perm (ac) -> bac , bca

c + perm (ab) -> кабіна , cba

perm (abc ... z) ->

a + perm (...), b + perm (....)
....

Я знайшов псевдокод на веб-сайті http://www.programmersheaven.com/mb/Algorithms/369713/369713/permutation-algorithm-help/ :

makePermutations(permutation) {
  if (length permutation < required length) {
    for (i = min digit to max digit) {
      if (i not in permutation) {
        makePermutations(permutation+i)
      }
    }
  }
  else {
    add permutation to list
  }
}

C #

Гаразд, і щось більш складне (і оскільки воно позначене c #), з http://radio.weblogs.com/0111551/stories/2002/10/14/permutations.html : Досить тривалий, але я вирішив скопіювати його у будь-якому випадку, тому публікація не залежить від оригіналу.

Функція приймає рядок символів і записує всі можливі перестановки цього точного рядка, так, наприклад, якщо "ABC" було поставлено, повинно випливати:

ABC, ACB, BAC, BCA, CAB, CBA.

Код:

class Program
{
    private static void Swap(ref char a, ref char b)
    {
        if (a == b) return;

        var temp = a;
        a = b;
        b = temp;
    }

    public static void GetPer(char[] list)
    {
        int x = list.Length - 1;
        GetPer(list, 0, x);
    }

    private static void GetPer(char[] list, int k, int m)
    {
        if (k == m)
        {
            Console.Write(list);
        }
        else
            for (int i = k; i <= m; i++)
            {
                   Swap(ref list[k], ref list[i]);
                   GetPer(list, k + 1, m);
                   Swap(ref list[k], ref list[i]);
            }
    }

    static void Main()
    {
        string str = "sagiv";
        char[] arr = str.ToCharArray();
        GetPer(arr);
    }
}

21
Для більшої ясності я б назвав k "recursionDepth" і зателефонував m "maxDepth".
Nerf Herder

3
2-й swap ( Swap(ref list[k], ref list[i]);) не є необхідним.
dance2die

1
Дякую за таке рішення. Я створив цю скрипку ( dotnetfiddle.net/oTzihw ) з неї (з належним називанням замість k і m). Наскільки я розумію algo, потрібен другий Swap (для зворотного відстеження), оскільки ви редагуєте початковий масив на місці.
Андрій

3
незначний момент: Схоже, метод Swap краще реалізовувати з тимчасовою змінною буфера і не використовувати XOR ( dotnetperls.com/swap )
Sergioet

7
Використовуючи кортежі C # 7, ви можете зробити своп набагато елегантнішим:(a[x], a[y]) = (a[y], a[x]);
Даррен

81

Це лише два рядки коду, якщо LINQ дозволено використовувати. Будь ласка, дивіться мою відповідь тут .

EDIT

Ось моя загальна функція, яка може повернути всі перестановки (а не комбінації) зі списку T:

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

Приклад:

IEnumerable<IEnumerable<int>> result =
    GetPermutations(Enumerable.Range(1, 3), 3);

Вихід - список цілих списків:

{1,2,3} {1,3,2} {2,1,3} {2,3,1} {3,1,2} {3,2,1}

Оскільки ця функція використовує LINQ, для цього потрібна .net 3.5 або новіша версія.


3
комбінації та перестановки - це різні речі. це схоже, але, здається, ваша відповідь відповідає на іншу проблему, ніж усі перестановки набору елементів.
Шон Ковач

@ShawnKovac, дякую за вказівку на це! Я оновив свій код від комбінації до перестановки.
Пеньян

1
@Pengyang Я переглянув вашу іншу відповідь, і скажу, що це мені дуже допомогло, але у мене є інша ситуація, про яку я не знаю, чи вказав ви правильний спосіб її вирішення. Я хотів знайти всі перестановки такого слова, як "HALLOWEEN", але виявив, що я також хочу включити і "L", і "E" в набір результатів. У своїх ітераціях я переглядаю ваш метод, що дає збільшену довжину з кожною ітерацією (довжина ++), і я очікую, що з огляду на повну довжину слова HALLOWEEN (9 символів) я отримаю результати завдовжки 9 символів ... але це не так: Я отримую лише 7 (1 л і 1 Е пропущено)
MegaMark

Я також хотів би зазначити, що я НЕ хочу, щоб ситуація, коли я бачу 9 'H як' H ', з’являється лише один раз у слові.
MegaMark

4
@MegaMark Ця функція вимагає, щоб елементи були унікальними. Спробуйте так:const string s = "HALLOWEEN"; var result = GetPermutations(Enumerable.Range(0, s.Length), s.Length).Select(t => t.Select(i => s[i]));
Pengyang

36

Тут я знайшов рішення. Це було написано на Java, але я перетворив його на C #. Сподіваюся, це допоможе тобі.

Введіть тут опис зображення

Ось код у C #:

static void Main(string[] args)
{
    string str = "ABC";
    char[] charArry = str.ToCharArray();
    Permute(charArry, 0, 2);
    Console.ReadKey();
}

static void Permute(char[] arry, int i, int n)
{
    int j;
    if (i==n)
        Console.WriteLine(arry);
    else
    {
        for(j = i; j <=n; j++)
        {
            Swap(ref arry[i],ref arry[j]);
            Permute(arry,i+1,n);
            Swap(ref arry[i], ref arry[j]); //backtrack
        }
    }
}

static void Swap(ref char a, ref char b)
{
    char tmp;
    tmp = a;
    a=b;
    b = tmp;
}

Це переноситься з іншої мови? Однозначно +1 для зображення, оскільки воно дійсно додає цінності. Однак, схоже, сам код має певний потенціал вдосконалення. Деякі незначні частини не потрібні, але найголовніше, що я відчуваю це C ++, коли ми щось надсилаємо і робимо щось замість того, щоб надавати параметри та отримувати повернене значення. Насправді я використовував ваше зображення, щоб реалізувати C # -стильований C #-код (звичайно, стиль є моїм особистим сприйняттям), і це мені дуже допомогло, тому коли я опублікую його, я обов'язково вкраду його у вас (і кредит ти за це).
Конрад Вільтерстен

21

Рекурсія не потрібна, ось хороша інформація про це рішення.

var values1 = new[] { 1, 2, 3, 4, 5 };

foreach (var permutation in values1.GetPermutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

var values2 = new[] { 'a', 'b', 'c', 'd', 'e' };

foreach (var permutation in values2.GetPermutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

Console.ReadLine();

Я використовую цей алгоритм протягом багатьох років, він має O (N) складність у часі та просторі для обчислення кожної перестановки .

public static class SomeExtensions
{
    public static IEnumerable<IEnumerable<T>> GetPermutations<T>(this IEnumerable<T> enumerable)
    {
        var array = enumerable as T[] ?? enumerable.ToArray();

        var factorials = Enumerable.Range(0, array.Length + 1)
            .Select(Factorial)
            .ToArray();

        for (var i = 0L; i < factorials[array.Length]; i++)
        {
            var sequence = GenerateSequence(i, array.Length - 1, factorials);

            yield return GeneratePermutation(array, sequence);
        }
    }

    private static IEnumerable<T> GeneratePermutation<T>(T[] array, IReadOnlyList<int> sequence)
    {
        var clone = (T[]) array.Clone();

        for (int i = 0; i < clone.Length - 1; i++)
        {
            Swap(ref clone[i], ref clone[i + sequence[i]]);
        }

        return clone;
    }

    private static int[] GenerateSequence(long number, int size, IReadOnlyList<long> factorials)
    {
        var sequence = new int[size];

        for (var j = 0; j < sequence.Length; j++)
        {
            var facto = factorials[sequence.Length - j];

            sequence[j] = (int)(number / facto);
            number = (int)(number % facto);
        }

        return sequence;
    }

    static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }

    private static long Factorial(int n)
    {
        long result = n;

        for (int i = 1; i < n; i++)
        {
            result = result * i;
        }

        return result;
    }
}

Працює з коробки!
revobtz

1
можливо я не розумію O (n) позначення. чи не посилається на N, скільки "внутрішніх циклів" потрібно, щоб алгоритм працював? мені здається, якщо у вас N номерів, здається, що це O (N * N!) (тому що N! разів він повинен робити N свопів). Плюс до цього потрібно виконати багато копіювання масиву. Цей код "акуратний", але я б його не використовував.
eric frazer

@ericfrazer Кожна перестановка використовує лише одну копію масиву, і O(N-1)для послідовності, і O(N)для свопів, що є O(N). І я все ще використовую це у виробництві, але з рефактором, щоб генерувати лише одну перестановку на зразок: GetPermutation(i)де 0 <= i <= N!-1. Я буду радий використовувати щось з кращою продуктивністю, ніж це, тому не соромтеся зателефонувати на довідку про щось краще, більшість альтернативних варіантів використовує O(N!)в пам'яті, щоб ви могли також перевірити це.
Наджера

11
void permute (char *str, int ptr) {
  int i, len;
  len = strlen(str);
  if (ptr == len) {
    printf ("%s\n", str);
    return;
  }

  for (i = ptr ; i < len ; i++) {
    swap (&str[ptr], &str[i]);
    permute (str, ptr + 1);
    swap (&str[ptr], &str[i]);
  }
}

Ви можете написати свою функцію swap, щоб поміняти символи.
Це слід називати перестановкою (рядок, 0);


5
Це схоже на C, а не на C #.
Джон Шнайдер

9

Перш за все, набори мають перестановки, а не рядки чи цілі числа, тому я просто припускаю, що ви маєте на увазі "набір символів у рядку".

Зауважте, що набір розміру n має n! n-перестановки.

Наступний псевдокод (з Вікіпедії), що називається з k = 1 ... n! дасть усі перестановки:

function permutation(k, s) {
    for j = 2 to length(s) {
        swap s[(k mod j) + 1] with s[j]; // note that our array is indexed starting at 1
        k := k / j; // integer division cuts off the remainder
    }
    return s;
}

Ось еквівалентний код Python (для індексів масиву на основі 0):

def permutation(k, s):
    r = s[:]
    for j in range(2, len(s)+1):
        r[j-1], r[k%j] = r[k%j], r[j-1]
        k = k/j+1
    return r

5
що це за мова? питання позначене C #. я не знаю, що k := k / j;робить.
Шон Ковач

8

Трохи модифікована версія в C #, яка дає необхідні перестановки в масиві будь-якого типу.

    // USAGE: create an array of any type, and call Permutations()
    var vals = new[] {"a", "bb", "ccc"};
    foreach (var v in Permutations(vals))
        Console.WriteLine(string.Join(",", v)); // Print values separated by comma


public static IEnumerable<T[]> Permutations<T>(T[] values, int fromInd = 0)
{
    if (fromInd + 1 == values.Length)
        yield return values;
    else
    {
        foreach (var v in Permutations(values, fromInd + 1))
            yield return v;

        for (var i = fromInd + 1; i < values.Length; i++)
        {
            SwapValues(values, fromInd, i);
            foreach (var v in Permutations(values, fromInd + 1))
                yield return v;
            SwapValues(values, fromInd, i);
        }
    }
}

private static void SwapValues<T>(T[] values, int pos1, int pos2)
{
    if (pos1 != pos2)
    {
        T tmp = values[pos1];
        values[pos1] = values[pos2];
        values[pos2] = tmp;
    }
}

Одне невелике застереження з цією реалізацією: воно працює належним чином лише якщо ви не намагаєтесь зберегти значення перерахунку. Якщо ви спробуєте зробити щось на кшталт, Permutations(vals).ToArray()тоді ви отримаєте N посилань на той самий масив. Якщо ви хочете мати можливість зберігати результати, вам потрібно створити копію вручну. Напр.Permutations(values).Select(v => (T[])v.Clone())
Фарап

8
class Program
{
    public static void Main(string[] args)
    {
        Permutation("abc");
    }

    static void Permutation(string rest, string prefix = "")
    {
        if (string.IsNullOrEmpty(rest)) Console.WriteLine(prefix);

        // Each letter has a chance to be permutated
        for (int i = 0; i < rest.Length; i++)
        {                
            Permutation(rest.Remove(i, 1), prefix + rest[i]);
        }
    }
}

1
Божевільний розчин. Дякую!
Крістіан Е.

7

Мені подобався підхід FBryant87, оскільки це просто. На жаль, воно, як і багато інших "рішень", не пропонує всіх перестановок або, наприклад, цілого числа, якщо воно містить одну і ту ж цифру більше одного разу. Візьміть 656123 як приклад. Лінія:

var tail = chars.Except(new List<char>(){c});

використовуючи Крім заподіє всі випадки повинні бути видалені, тобто , коли з = 6, дві цифри видаляються , і ми залишилися з , наприклад , 5123. Оскільки жоден з рішень я намагався вирішити це, я вирішив спробувати і вирішити сам по FBryant87 «з код як базовий. Ось що я придумав:

private static List<string> FindPermutations(string set)
    {
        var output = new List<string>();
        if (set.Length == 1)
        {
            output.Add(set);
        }
        else
        {
            foreach (var c in set)
            {
                // Remove one occurrence of the char (not all)
                var tail = set.Remove(set.IndexOf(c), 1);
                foreach (var tailPerms in FindPermutations(tail))
                {
                    output.Add(c + tailPerms);
                }
            }
        }
        return output;
    }

Я просто видаляю перше знайдене явище за допомогою .Remove та .IndexOf. Здається, що працює як мінімум для мого використання. Я впевнений, що це може бути розумнішим.

Однак слід зазначити одне: Отриманий список може містити дублікати, тому переконайтеся, що ви або зробите метод повернення, наприклад, HashSet замість цього, або видаліть дублікати після повернення, використовуючи будь-який метод, який вам подобається.


Діє як абсолютна краса, спершу я виявив, що обробляє дублікати символів +1
Джек Кейсі


5

Ось суто функціональна реалізація F #:


let factorial i =
    let rec fact n x =
        match n with
        | 0 -> 1
        | 1 -> x
        | _ -> fact (n-1) (x*n)
    fact i 1

let swap (arr:'a array) i j = [| for k in 0..(arr.Length-1) -> if k = i then arr.[j] elif k = j then arr.[i] else arr.[k] |]

let rec permutation (k:int,j:int) (r:'a array) =
    if j = (r.Length + 1) then r
    else permutation (k/j+1, j+1) (swap r (j-1) (k%j))

let permutations (source:'a array) = seq { for k = 0 to (source |> Array.length |> factorial) - 1 do yield permutation (k,2) source }

Продуктивність може бути значно покращена шляхом зміни swap, щоб скористатися змінним характером CLR-масивів, але ця реалізація є безпечною для потоків стосовно вихідного масиву, і це може бути бажано в деяких контекстах. Крім того, для масивів з більш ніж 16 елементами int слід замінити на типи з більшою / довільною точністю, оскільки факторіальна 17 призводить до переповнення int32.


5

Ось просте рішення в c # за допомогою рекурсії,

void Main()
{
    string word = "abc";
    WordPermuatation("",word);
}

void WordPermuatation(string prefix, string word)
{
    int n = word.Length;
    if (n == 0) { Console.WriteLine(prefix); }
    else
    {
        for (int i = 0; i < n; i++)
        {
            WordPermuatation(prefix + word[i],word.Substring(0, i) + word.Substring(i + 1, n - (i+1)));
        }
    }
}

Дякую за дуже просте і коротке рішення! :)
Kristaps Vilerts

4

Ось проста для розуміння функція перестановки для рядка та цілого числа як вхідного. За допомогою цього ви навіть можете встановити свою вихідну довжину (що в звичайному випадку вона дорівнює довжині введення)

Рядок

    static ICollection<string> result;

    public static ICollection<string> GetAllPermutations(string str, int outputLength)
    {
        result = new List<string>();
        MakePermutations(str.ToCharArray(), string.Empty, outputLength);
        return result;
    }

    private static void MakePermutations(
       char[] possibleArray,//all chars extracted from input
       string permutation,
       int outputLength//the length of output)
    {
         if (permutation.Length < outputLength)
         {
             for (int i = 0; i < possibleArray.Length; i++)
             {
                 var tempList = possibleArray.ToList<char>();
                 tempList.RemoveAt(i);
                 MakePermutations(tempList.ToArray(), 
                      string.Concat(permutation, possibleArray[i]), outputLength);
             }
         }
         else if (!result.Contains(permutation))
            result.Add(permutation);
    }

а для Integer просто змініть метод виклику, і MakePermutations () залишається недоторканим:

    public static ICollection<int> GetAllPermutations(int input, int outputLength)
    {
        result = new List<string>();
        MakePermutations(input.ToString().ToCharArray(), string.Empty, outputLength);
        return result.Select(m => int.Parse(m)).ToList<int>();
    }

приклад 1: GetAllPermutations ("abc", 3); "abc" "acb" "bac" "bca" "cab" "cba"

приклад 2: GetAllPermutations ("abcd", 2); "ab" "ac" "ad" "ba" "bc" "bd" "ca" "cb" "cd" "da" "db" "dc"

приклад 3: GetAllPermutations (486,2); 48 46 84 86 64 68


Мені подобається ваше рішення, тому що це легко зрозуміти, дякую за це! Але я вирішив піти з цим: stackoverflow.com/questions/756055/… . Причина в тому, що всі ToList, ToArray і RemoveAt мають складну часову складність O (N). Тому в основному вам доведеться переглядати всі елементи колекції (див. Stackoverflow.com/a/15042066/1132522 ). Те саме для int, де ви в основному знову перебираєте всі елементи в кінці, щоб перетворити їх у int. Я погоджуюся, що це не має особливого впливу для "abc" або 486, хоча.
Андрій

2

Ось функція, яка буде надрукувати всі перестановки. Ця функція реалізує логіку, пояснену peter.

public class Permutation
{
    //http://www.java2s.com/Tutorial/Java/0100__Class-Definition/RecursivemethodtofindallpermutationsofaString.htm

    public static void permuteString(String beginningString, String endingString)
    {           

        if (endingString.Length <= 1)
            Console.WriteLine(beginningString + endingString);
        else
            for (int i = 0; i < endingString.Length; i++)
            {

                String newString = endingString.Substring(0, i) + endingString.Substring(i + 1);

                permuteString(beginningString + endingString.ElementAt(i), newString);

            }
    }
}

    static void Main(string[] args)
    {

        Permutation.permuteString(String.Empty, "abc");
        Console.ReadLine();

    }

2

Нижче - моя реалізація перестановки. Не заважайте назви змінних, як я це робив для задоволення :)

class combinations
{
    static void Main()
    {

        string choice = "y";
        do
        {
            try
            {
                Console.WriteLine("Enter word :");
                string abc = Console.ReadLine().ToString();
                Console.WriteLine("Combinatins for word :");
                List<string> final = comb(abc);
                int count = 1;
                foreach (string s in final)
                {
                    Console.WriteLine("{0} --> {1}", count++, s);
                }
                Console.WriteLine("Do you wish to continue(y/n)?");
                choice = Console.ReadLine().ToString();
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        } while (choice == "y" || choice == "Y");
    }

    static string swap(string test)
    {
        return swap(0, 1, test);
    }

    static List<string> comb(string test)
    {
        List<string> sec = new List<string>();
        List<string> first = new List<string>();
        if (test.Length == 1) first.Add(test);
        else if (test.Length == 2) { first.Add(test); first.Add(swap(test)); }
        else if (test.Length > 2)
        {
            sec = generateWords(test);
            foreach (string s in sec)
            {
                string init = s.Substring(0, 1);
                string restOfbody = s.Substring(1, s.Length - 1);

                List<string> third = comb(restOfbody);
                foreach (string s1 in third)
                {
                    if (!first.Contains(init + s1)) first.Add(init + s1);
                }


            }
        }

        return first;
    }

    static string ShiftBack(string abc)
    {
        char[] arr = abc.ToCharArray();
        char temp = arr[0];
        string wrd = string.Empty;
        for (int i = 1; i < arr.Length; i++)
        {
            wrd += arr[i];
        }

        wrd += temp;
        return wrd;
    }

    static List<string> generateWords(string test)
    {
        List<string> final = new List<string>();
        if (test.Length == 1)
            final.Add(test);
        else
        {
            final.Add(test);
            string holdString = test;
            while (final.Count < test.Length)
            {
                holdString = ShiftBack(holdString);
                final.Add(holdString);
            }
        }

        return final;
    }

    static string swap(int currentPosition, int targetPosition, string temp)
    {
        char[] arr = temp.ToCharArray();
        char t = arr[currentPosition];
        arr[currentPosition] = arr[targetPosition];
        arr[targetPosition] = t;
        string word = string.Empty;
        for (int i = 0; i < arr.Length; i++)
        {
            word += arr[i];

        }

        return word;

    }
}

2

Ось я написав приклад високого рівня, який ілюструє пояснення людської мови :

    public List<string> FindPermutations(string input)
    {
        if (input.Length == 1)
            return new List<string> { input };
        var perms = new List<string>();
        foreach (var c in input)
        {
            var others = input.Remove(input.IndexOf(c), 1);
            perms.AddRange(FindPermutations(others).Select(perm => c + perm));
        }
        return perms;
    }

Це рішення насправді є недоліком у тому, що якщо набір рядків містить будь-які повторювані символи, він не вдасться. Наприклад, у слові 'test' команда Except видалить обидва екземпляри 't' замість лише першого та останнього, коли це необхідно.
Middas

1
@Middas добре помічений, на щастя, обійняти придумали рішення для вирішення цього питання.
FBryant87

1

Якщо продуктивність і пам’ять - це проблема, я пропоную цю дуже ефективну реалізацію. За алгоритмом Хіпа у Вікіпедії , він повинен бути найшвидшим. Сподіваюсь, це відповідатиме вашим потребам :-)!

Так само, як порівняння цього з реалізацією Linq за 10! (код включений):

  • Це: 36288000 предметів у 235 мілісек
  • Linq: 36288000 предметів у 50051 мілісек

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    
    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
            /// </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) =>
                {
                    Debug.Print(String.Join("", vals));
                    return false;
                });
    
                int[] values = new int[] { 0, 1, 2, 4 };
    
                Debug.Print("Non Linq");
                ForAllPermutation(values, (vals) =>
                {
                    Debug.Print(String.Join("", vals));
                    return false;
                });
    
                Debug.Print("Linq");
                foreach(var v in GetPermutations(values, values.Length))
                {
                    Debug.Print(String.Join("", v));
                }
    
                // Performance
                int count = 0;
    
                values = new int[10];
                for(int n = 0; n < values.Length; n++)
                {
                    values[n] = n;
                }
    
                Stopwatch stopWatch = new Stopwatch();
                stopWatch.Reset();
                stopWatch.Start();
    
                ForAllPermutation(values, (vals) =>
                {
                    foreach(var v in vals)
                    {
                        count++;
                    }
                    return false;
                });
    
                stopWatch.Stop();
                Debug.Print($"Non Linq {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();
                Debug.Print($"Linq {count} items in {stopWatch.ElapsedMilliseconds} millisecs");
    
            }
        }
    }

1

Ось моє рішення в JavaScript (NodeJS). Основна ідея полягає в тому, що ми беремо по одному елементу, "виймаємо" його з рядка, змінюємо решту символів і вставляємо елемент спереду.

function perms (string) {
  if (string.length == 0) {
    return [];
  }
  if (string.length == 1) {
    return [string];
  }
  var list = [];
  for(var i = 0; i < string.length; i++) {
    var invariant = string[i];
    var rest = string.substr(0, i) + string.substr(i + 1);
    var newPerms = perms(rest);
    for (var j = 0; j < newPerms.length; j++) {
      list.push(invariant + newPerms[j]);
    }
  }
  return list;
}

module.exports = perms;

І ось тести:

require('should');
var permutations = require('../src/perms');

describe('permutations', function () {
  it('should permute ""', function () {
    permutations('').should.eql([]);
  })

  it('should permute "1"', function () {
    permutations('1').should.eql(['1']);
  })

  it('should permute "12"', function () {
    permutations('12').should.eql(['12', '21']);
  })

  it('should permute "123"', function () {
    var expected = ['123', '132', '321', '213', '231', '312'];
    var actual = permutations('123');
    expected.forEach(function (e) {
      actual.should.containEql(e);
    })
  })

  it('should permute "1234"', function () {
    // Wolfram Alpha FTW!
    var expected = ['1234', '1243', '1324', '1342', '1423', '1432', '2134', '2143', '2314', '2341', '2413', '2431', '3124', '3142', '3214', '3241', '3412', '3421', '4123', '4132'];
    var actual = permutations('1234');
    expected.forEach(function (e) {
      actual.should.containEql(e);
    })
  })
})

1

Ось найпростіше рішення, про яке я можу придумати:

let rec distribute e = function
  | [] -> [[e]]
  | x::xs' as xs -> (e::xs)::[for xs in distribute e xs' -> x::xs]

let permute xs = Seq.fold (fun ps x -> List.collect (distribute x) ps) [[]] xs

distributeФункція приймає новий елемент eі n-елементний список і повертає список n+1списків , кожен з яких має eвставлені в іншому місці. Наприклад, вставляючи 10кожне з чотирьох можливих місць у списку [1;2;3]:

> distribute 10 [1..3];;
val it : int list list =
  [[10; 1; 2; 3]; [1; 10; 2; 3]; [1; 2; 10; 3]; [1; 2; 3; 10]]

permuteФункція складки над кожним елементом в своїй черзі , розподіляє над перестановками , накопичених до сих пір, кульмінація всіх перестановок. Наприклад, 6 перестановок списку [1;2;3]:

> permute [1;2;3];;
val it : int list list =
  [[3; 2; 1]; [2; 3; 1]; [2; 1; 3]; [3; 1; 2]; [1; 3; 2]; [1; 2; 3]]

Зміна foldна "a" scan, щоб утримати проміжні акумулятори, проливають деяке світло на те, як перестановки породжують елемент за один раз:

> Seq.scan (fun ps x -> List.collect (distribute x) ps) [[]] [1..3];;
val it : seq<int list list> =
  seq
    [[[]]; [[1]]; [[2; 1]; [1; 2]];
     [[3; 2; 1]; [2; 3; 1]; [2; 1; 3]; [3; 1; 2]; [1; 3; 2]; [1; 2; 3]]]

1

Перелічує перестановки рядка. Уникає дублювання, коли символи повторюються:

using System;
using System.Collections;

class Permutation{
  static IEnumerable Permutations(string word){
    if (word == null || word.Length <= 1) {
      yield return word;
      yield break;
    }

    char firstChar = word[0];
    foreach( string subPermute in Permutations (word.Substring (1)) ) {
      int indexOfFirstChar = subPermute.IndexOf (firstChar);
      if (indexOfFirstChar == -1) indexOfFirstChar = subPermute.Length;

      for( int index = 0; index <= indexOfFirstChar; index++ )
        yield return subPermute.Insert (index, new string (firstChar, 1));
    }
  }

  static void Main(){
    foreach( var permutation in Permutations ("aab") )
      Console.WriteLine (permutation);
  }
}

2
Оскільки вже існує стільки робочих рішень, ви можете описати те, що робить ваше рішення виділенням від усіх інших тут рішень.
nvoigt

Уникає дублювання, коли символи повторюються (хіндірала для іншої відповіді). Для "aab": aab aba baa
Val

1

Спираючись на рішення @ Peter, ось версія, яка оголошує простий Permutations()метод розширення у стилі LINQ, який працює на будь-якомуIEnumerable<T> .

Використання (на прикладі символів рядків):

foreach (var permutation in "abc".Permutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

Виходи:

a, b, c
a, c, b
b, a, c
b, c, a
c, b, a
c, a, b

Або для будь-якого іншого типу колекції:

foreach (var permutation in (new[] { "Apples", "Oranges", "Pears"}).Permutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

Виходи:

Apples, Oranges, Pears
Apples, Pears, Oranges
Oranges, Apples, Pears
Oranges, Pears, Apples
Pears, Oranges, Apples
Pears, Apples, Oranges
using System;
using System.Collections.Generic;
using System.Linq;

public static class PermutationExtension
{
    public static IEnumerable<T[]> Permutations<T>(this IEnumerable<T> source)
    {
        var sourceArray = source.ToArray();
        var results = new List<T[]>();
        Permute(sourceArray, 0, sourceArray.Length - 1, results);
        return results;
    }

    private static void Swap<T>(ref T a, ref T b)
    {
        T tmp = a;
        a = b;
        b = tmp;
    }

    private static void Permute<T>(T[] elements, int recursionDepth, int maxDepth, ICollection<T[]> results)
    {
        if (recursionDepth == maxDepth)
        {
            results.Add(elements.ToArray());
            return;
        }

        for (var i = recursionDepth; i <= maxDepth; i++)
        {
            Swap(ref elements[recursionDepth], ref elements[i]);
            Permute(elements, recursionDepth + 1, maxDepth, results);
            Swap(ref elements[recursionDepth], ref elements[i]);
        }
    }
}

0

Ось функція, яка буде друкувати всі перестановки рекурсивно.

public void Permutations(string input, StringBuilder sb)
    {
        if (sb.Length == input.Length)
        {
            Console.WriteLine(sb.ToString());
            return;
        }

        char[] inChar = input.ToCharArray();

        for (int i = 0; i < input.Length; i++)
        {
            if (!sb.ToString().Contains(inChar[i]))
            {
                sb.Append(inChar[i]);
                Permutations(input, sb);    
                RemoveChar(sb, inChar[i]);
            }
        }
    }

private bool RemoveChar(StringBuilder input, char toRemove)
    {
        int index = input.ToString().IndexOf(toRemove);
        if (index >= 0)
        {
            input.Remove(index, 1);
            return true;
        }
        return false;
    }

0
class Permutation
{
    public static List<string> Permutate(string seed, List<string> lstsList)
    {
        loopCounter = 0;
        // string s="\w{0,2}";
        var lstStrs = PermuateRecursive(seed);

        Trace.WriteLine("Loop counter :" + loopCounter);
        return lstStrs;
    }

    // Recursive function to find permutation
    private static List<string> PermuateRecursive(string seed)
    {
        List<string> lstStrs = new List<string>();

        if (seed.Length > 2)
        {
            for (int i = 0; i < seed.Length; i++)
            {
                str = Swap(seed, 0, i);

                PermuateRecursive(str.Substring(1, str.Length - 1)).ForEach(
                    s =>
                    {
                        lstStrs.Add(str[0] + s);
                        loopCounter++;
                    });
                ;
            }
        }
        else
        {
            lstStrs.Add(seed);
            lstStrs.Add(Swap(seed, 0, 1));
        }
        return lstStrs;
    }
    //Loop counter variable to count total number of loop execution in various functions
    private static int loopCounter = 0;

    //Non recursive  version of permuation function
    public static List<string> Permutate(string seed)
    {
        loopCounter = 0;
        List<string> strList = new List<string>();
        strList.Add(seed);
        for (int i = 0; i < seed.Length; i++)
        {
            int count = strList.Count;
            for (int j = i + 1; j < seed.Length; j++)
            {
                for (int k = 0; k < count; k++)
                {
                    strList.Add(Swap(strList[k], i, j));
                    loopCounter++;
                }
            }
        }
        Trace.WriteLine("Loop counter :" + loopCounter);
        return strList;
    }

    private static string Swap(string seed, int p, int p2)
    {
        Char[] chars = seed.ToCharArray();
        char temp = chars[p2];
        chars[p2] = chars[p];
        chars[p] = temp;
        return new string(chars);
    }
}

0

Ось відповідь на C #, яка трохи спрощена.

public static void StringPermutationsDemo()
{
    strBldr = new StringBuilder();

    string result = Permute("ABCD".ToCharArray(), 0);
    MessageBox.Show(result);
}     

static string Permute(char[] elementsList, int startIndex)
{
    if (startIndex == elementsList.Length)
    {
        foreach (char element in elementsList)
        {
            strBldr.Append(" " + element);
        }
        strBldr.AppendLine("");
    }
    else
    {
        for (int tempIndex = startIndex; tempIndex <= elementsList.Length - 1; tempIndex++)
        {
            Swap(ref elementsList[startIndex], ref elementsList[tempIndex]);

            Permute(elementsList, (startIndex + 1));

            Swap(ref elementsList[startIndex], ref elementsList[tempIndex]);
        }
    }

    return strBldr.ToString();
}

static void Swap(ref char Char1, ref char Char2)
{
    char tempElement = Char1;
    Char1 = Char2;
    Char2 = tempElement;
}

Вихід:

1 2 3
1 3 2

2 1 3
2 3 1

3 2 1
3 1 2

0

Це моє рішення, яке мені легко зрозуміти

class ClassicPermutationProblem
{
    ClassicPermutationProblem() { }

    private static void PopulatePosition<T>(List<List<T>> finalList, List<T> list, List<T> temp, int position)
    {
         foreach (T element in list)
         {
             List<T> currentTemp = temp.ToList();
             if (!currentTemp.Contains(element))
                currentTemp.Add(element);
             else
                continue;

             if (position == list.Count)
                finalList.Add(currentTemp);
             else
                PopulatePosition(finalList, list, currentTemp, position + 1);
        }
    }

    public static List<List<int>> GetPermutations(List<int> list)
    {
        List<List<int>> results = new List<List<int>>();
        PopulatePosition(results, list, new List<int>(), 1);
        return results;
     }
}

static void Main(string[] args)
{
    List<List<int>> results = ClassicPermutationProblem.GetPermutations(new List<int>() { 1, 2, 3 });
}

0

Ось ще одна реалізація згаданого альго.

public class Program
{
    public static void Main(string[] args)
    {
        string str = "abcefgh";
        var astr = new Permutation().GenerateFor(str);
        Console.WriteLine(astr.Length);
        foreach(var a in astr)
        {
            Console.WriteLine(a);
        }
        //a.ForEach(Console.WriteLine);
    }
}

class Permutation
{
    public string[] GenerateFor(string s)
    {  

        if(s.Length == 1)
        {

            return new []{s}; 
        }

        else if(s.Length == 2)
        {

            return new []{s[1].ToString()+s[0].ToString(),s[0].ToString()+s[1].ToString()};

        }

        var comb = new List<string>();

        foreach(var c in s)
        {

            string cStr = c.ToString();

            var sToProcess = s.Replace(cStr,"");
            if (!string.IsNullOrEmpty(sToProcess) && sToProcess.Length>0)
            {
                var conCatStr = GenerateFor(sToProcess);



                foreach(var a in conCatStr)
                {
                    comb.Add(c.ToString()+a);
                }


            }
        }
        return comb.ToArray();

    }
}

new Permutation().GenerateFor("aba")виходиstring[4] { "ab", "baa", "baa", "ab" }
Атомоск

0
    //Generic C# Method
            private static List<T[]> GetPerms<T>(T[] input, int startIndex = 0)
            {
                var perms = new List<T[]>();

                var l = input.Length - 1;

                if (l == startIndex)
                    perms.Add(input);
                else
                {

                    for (int i = startIndex; i <= l; i++)
                    {
                        var copy = input.ToArray(); //make copy

                        var temp = copy[startIndex];

                        copy[startIndex] = copy[i];
                        copy[i] = temp;

                        perms.AddRange(GetPerms(copy, startIndex + 1));

                    }
                }

                return perms;
            }

            //usages
            char[] charArray = new char[] { 'A', 'B', 'C' };
            var charPerms = GetPerms(charArray);


            string[] stringArray = new string[] { "Orange", "Mango", "Apple" };
            var stringPerms = GetPerms(stringArray);


            int[] intArray = new int[] { 1, 2, 3 };
            var intPerms = GetPerms(intArray);

Було б чудово, якщо ви зможете трохи детальніше розповісти про те, як працює цей код, а не залишати його тут у спокої.
iBug

-1
    /// <summary>
    /// Print All the Permutations.
    /// </summary>
    /// <param name="inputStr">input string</param>
    /// <param name="strLength">length of the string</param>
    /// <param name="outputStr">output string</param>
    private void PrintAllPermutations(string inputStr, int strLength,string outputStr, int NumberOfChars)
    {
        //Means you have completed a permutation.
        if (outputStr.Length == NumberOfChars)
        {
            Console.WriteLine(outputStr);                
            return;
        }

        //For loop is used to print permutations starting with every character. first print all the permutations starting with a,then b, etc.
        for(int i=0 ; i< strLength; i++)
        {
            // Recursive call : for a string abc = a + perm(bc). b+ perm(ac) etc.
            PrintAllPermutations(inputStr.Remove(i, 1), strLength - 1, outputStr + inputStr.Substring(i, 1), 4);
        }
    }        
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.