Liệt kê tất cả các hoán vị của một chuỗi / số nguyên


159

Một nhiệm vụ phổ biến trong các cuộc phỏng vấn lập trình (không phải từ kinh nghiệm phỏng vấn của tôi) là lấy một chuỗi hoặc một số nguyên và liệt kê mọi hoán vị có thể.

Có một ví dụ về cách thức này được thực hiện và logic đằng sau việc giải quyết vấn đề như vậy?

Tôi đã thấy một vài đoạn mã nhưng họ không nhận xét / giải thích tốt và do đó khó theo dõi.


1
Dưới đây là một câu hỏi cho phép với một số câu trả lời giải thích tốt, bao gồm cả biểu đồ , nhưng không có trong C #.
người dùng không xác định

Câu trả lời:


152

Trước hết: nó có mùi như đệ quy tất nhiên!

Vì bạn cũng muốn biết nguyên tắc, tôi đã làm hết sức mình để giải thích nó bằng ngôn ngữ của con người. Tôi nghĩ rằng hầu hết các lần đệ quy là rất dễ dàng. Bạn chỉ cần nắm hai bước:

  1. Bước đầu tiên
  2. Tất cả các bước khác (tất cả đều có cùng logic)

Trong ngôn ngữ của con người :

Tóm lại:
1. Hoán vị của 1 phần tử là một phần tử.
2. Hoán vị của một tập hợp các phần tử là một danh sách mỗi phần tử, được nối với mọi hoán vị của các phần tử khác.

Thí dụ:

Nếu tập hợp chỉ có một phần tử ->
trả lại nó.
perm (a) -> a

Nếu tập hợp có hai ký tự: cho mỗi phần tử trong đó: trả về phần tử, với hoán vị của phần tử còn lại được thêm vào, như vậy:

perm (ab) ->

a + perm (b) -> ab

b + perm (a) -> ba

Hơn nữa: đối với mỗi ký tự trong tập hợp: trả về một ký tự, được nối với một khoảng trống> phần còn lại của tập hợp

perm (abc) ->

a + perm (bc) -> abc , acb

b + perm (ac) -> bac , bca

c + perm (ab) -> taxi , cba

perm (abc ... z) ->

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

Tôi đã tìm thấy mã giả trên http://www.programmersheaven.com/mb/AlacticmsLM9713 / 13979713 / mututing-alerskym-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 #

OK, và một cái gì đó chi tiết hơn (và vì nó được gắn thẻ c #), từ http://radio.weblogs.com/0111551/stories/2002/10/14/permutations.html : Khá dài, nhưng tôi đã quyết định sao chép nó Dù sao đi nữa, vì vậy bài viết không phụ thuộc vào bản gốc.

Hàm lấy một chuỗi ký tự và ghi lại mọi hoán vị có thể có của chuỗi chính xác đó, vì vậy, ví dụ, nếu "ABC" đã được cung cấp, sẽ tràn ra:

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

Mã số:

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
Để rõ ràng hơn một chút, tôi sẽ gọi k "recursionDepth" và gọi m "maxDepth".
Nerf Herder

3
Hoán đổi thứ 2 ( Swap(ref list[k], ref list[i]);) là không cần thiết.
dance2die

1
Cảm ơn bạn cho giải pháp này. Tôi đã tạo ra fiddle này ( dotnetfiddle.net/oTzihw ) từ nó (với cách đặt tên thích hợp thay vì k và m). Theo như tôi hiểu về thuật toán, Swap thứ hai là bắt buộc (để quay lại) kể từ khi bạn chỉnh sửa mảng ban đầu tại chỗ.
Andrew

3
một điểm nhỏ: Dường như các phương pháp hoán đổi là tốt hơn để được thực hiện với một biến đệm tạm thời và không sử dụng XORs ( dotnetperls.com/swap )
Sergioet

7
Sử dụng bộ dữ liệu C # 7, bạn có thể thực hiện trao đổi thanh lịch hơn rất nhiều:(a[x], a[y]) = (a[y], a[x]);
Darren

81

Đó chỉ là hai dòng mã nếu LINQ được phép sử dụng. Xin vui lòng xem câu trả lời của tôi ở đây .

BIÊN TẬP

Đây là hàm chung của tôi có thể trả về tất cả các hoán vị (không phải kết hợp) từ danh sách 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 }));
}

Thí dụ:

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

Đầu ra - một danh sách các danh sách số nguyên:

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

Vì hàm này sử dụng LINQ nên nó yêu cầu .net 3.5 trở lên.


3
kết hợp và hoán vị là những thứ khác nhau. nó tương tự nhau, nhưng câu trả lời của bạn dường như đang trả lời một vấn đề khác với tất cả các hoán vị của một tập hợp các phần tử.
Shawn Kovac

@ShawnKovac, cảm ơn bạn đã chỉ ra điều này! Tôi đã cập nhật mã của mình từ kết hợp sang hoán vị.
Bành Dương

1
@Pengyang Tôi đã xem câu trả lời khác của bạn và tôi sẽ nói rằng điều này đã giúp tôi rất nhiều nhưng tôi có một tình huống khác mà tôi không biết nếu bạn chỉ ra cách giải quyết chính xác. Tôi muốn tìm tất cả các hoán vị của một từ như 'HALLOWEEN' nhưng thấy rằng tôi cũng muốn bao gồm cả 'L và cả' E 'trong tập kết quả. Trong các lần lặp của tôi, tôi lặp lại phương pháp của bạn để tăng độ dài với mỗi lần lặp (độ dài ++) và hy vọng rằng với độ dài đầy đủ của từ HALLOWEEN (9 ký tự) tôi sẽ nhận được kết quả dài 9 ký tự ... nhưng đây không phải là trường hợp: Tôi chỉ nhận được 7 (1 L và 1 E bị bỏ qua)
MegaMark

Tôi cũng muốn chỉ ra rằng tôi KHÔNG muốn một tình huống mà tôi thấy 9 'H là' H 'chỉ xuất hiện một lần trong từ.
MegaMark

4
@MegaMark Chức năng này yêu cầu các yếu tố là duy nhất. Hãy thử điều này:const string s = "HALLOWEEN"; var result = GetPermutations(Enumerable.Range(0, s.Length), s.Length).Select(t => t.Select(i => s[i]));
Bành Dương

36

Ở đây tôi đã tìm thấy giải pháp. Nó được viết bằng Java, nhưng tôi đã chuyển đổi nó thành C #. Tôi hy vọng nó sẽ giúp bạn.

Nhập mô tả hình ảnh ở đây

Đây là mã trong 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;
}

Có phải nó được chuyển từ ngôn ngữ khác? Chắc chắn +1 cho hình ảnh, bởi vì nó thực sự tăng thêm giá trị. Tuy nhiên, bản thân mã dường như có một tiềm năng cải tiến nhất định. Một số phần nhỏ không cần thiết nhưng quan trọng nhất là tôi đang có cảm giác C ++ này khi chúng tôi gửi một cái gì đó và làm công cụ cho nó thay vì cung cấp các tham số và lấy giá trị trả về. Trên thực tế, tôi đã sử dụng hình ảnh của bạn để triển khai mã C # -styled (tất nhiên là phong cách là nhận thức cá nhân của tôi) và nó đã hỗ trợ tôi rất nhiều, vì vậy khi tôi đăng nó chắc chắn tôi sẽ đánh cắp nó (và tín dụng bạn cho nó).
Konrad Viltersten

21

Đệ quy là không cần thiết, đây là thông tin tốt về giải pháp này.

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

Tôi đã sử dụng thuật toán này trong nhiều năm, nó có thời giankhông gian O (N) độ phức tạp về để tính toán từng hoán vị .

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

Sáng tạo!
revazedz

1
có lẽ tôi không hiểu ký hiệu O (n). N không đề cập đến bao nhiêu "vòng lặp bên trong" là cần thiết để làm cho thuật toán của bạn hoạt động? đối với tôi có vẻ như nếu bạn có số N, có vẻ như đó là O (N * N!) (vì N! lần nó phải thực hiện hoán đổi N). Thêm vào đó, nó phải thực hiện một tấn sao chép mảng. Mã này là "gọn gàng", nhưng tôi sẽ không sử dụng nó.
eric frazer

@ericfrazer Mỗi hoán vị chỉ sử dụng một bản sao mảng, và O(N-1)cho chuỗi và O(N)cho các giao dịch hoán đổi, đó là O(N). Và tôi vẫn đang sử dụng điều này trong sản xuất nhưng với một bộ tái cấu trúc để chỉ tạo ra một hoán vị như: GetPermutation(i)ở đâu 0 <= i <= N!-1. Tôi sẽ rất vui khi sử dụng một cái gì đó có hiệu suất tốt hơn cái này, vì vậy hãy thoải mái gọi một tài liệu tham khảo cho một cái gì đó tốt hơn, hầu hết các lựa chọn thay thế sử dụng O(N!)trong bộ nhớ để bạn cũng có thể kiểm tra điều đó.
Najera

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

Bạn có thể viết chức năng hoán đổi của bạn để hoán đổi các ký tự.
Điều này được gọi là permute (chuỗi, 0);


5
Điều này trông giống như C, không phải C #.
Jon Schneider

9

Trước hết, các tập hợp có hoán vị, không phải chuỗi hoặc số nguyên, vì vậy tôi sẽ giả sử bạn có nghĩa là "tập hợp các ký tự trong một chuỗi."

Lưu ý rằng một bộ kích thước n có n! n-hoán vị.

Mã giả sau (từ Wikipedia), được gọi với k = 1 ... n! sẽ cho tất cả các hoán vị:

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

Đây là mã Python tương đương (cho các chỉ mục mảng dựa trên 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
đây là ngôn ngữ gì câu hỏi được đánh dấu C #. tôi không biết những gì k := k / j;không.
Shawn Kovac

8

Phiên bản sửa đổi một chút trong C # mang lại hoán vị cần thiết trong một mảng kiểu BẤT K ..

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

Một lưu ý nhỏ với việc triển khai này: nó chỉ hoạt động đúng nếu bạn không cố lưu trữ giá trị liệt kê. Nếu bạn cố gắng làm một cái gì đó như thế Permutations(vals).ToArray()thì bạn kết thúc với N tham chiếu đến cùng một mảng. Nếu bạn muốn lưu trữ kết quả, bạn phải tự tạo một bản sao. Ví dụ:Permutations(values).Select(v => (T[])v.Clone())
Pharap

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
Giải pháp điên rồ. Cảm ơn bạn!
Cristian E.

7

Tôi thích cách tiếp cận FBryant87 vì nó đơn giản. Thật không may, nó giống như nhiều "giải pháp" khác không cung cấp tất cả các hoán vị hoặc ví dụ như một số nguyên nếu nó chứa cùng một chữ số nhiều lần. Lấy 656123 làm ví dụ. Dòng:

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

sử dụng Trừ sẽ gây ra tất cả các lần xuất hiện để được gỡ bỏ, tức là khi c = 6, hai chữ số được lấy ra và chúng tôi là trái với ví dụ 5123. Kể từ khi không ai trong số các giải pháp tôi đã cố gắng giải quyết điều này, tôi quyết định thử và giải quyết nó bản thân mình bởi FBryant87 's mã làm cơ sở. Đây là những gì tôi nghĩ ra:

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

Tôi chỉ đơn giản là loại bỏ sự xuất hiện đầu tiên được tìm thấy bằng cách sử dụng .Remove và. IndexOf. Có vẻ như để làm việc như dự định cho việc sử dụng của tôi ít nhất. Tôi chắc chắn rằng nó có thể được thực hiện thông minh hơn.

Mặc dù vậy, một điều cần lưu ý: Danh sách kết quả có thể chứa các bản sao, vì vậy hãy đảm bảo bạn thực hiện trả về phương thức, ví dụ Hash Hash thay thế hoặc xóa các bản sao sau khi trả về bằng bất kỳ phương thức nào bạn muốn.


Hoạt động như một vẻ đẹp tuyệt đối, đầu tiên tôi đã tìm thấy xử lý các nhân vật trùng lặp +1
Jack Casey


5

Đây là một triển khai F # hoàn toàn có chức năng:


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 }

Hiệu suất có thể được cải thiện đáng kể bằng cách thay đổi hoán đổi để tận dụng tính chất có thể thay đổi của mảng CLR, nhưng việc triển khai này là luồng an toàn liên quan đến mảng nguồn và có thể được mong muốn trong một số bối cảnh. Ngoài ra, đối với các mảng có nhiều hơn 16 phần tử int phải được thay thế bằng các loại có độ chính xác lớn hơn / tùy ý vì giai đoạn 17 dẫn đến một tràn int32.


5

Đây là một giải pháp đơn giản trong c # bằng cách sử dụng đệ quy,

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

Cảm ơn bạn cho giải pháp rất đơn giản và ngắn! :)
Kristaps Vilerts

4

Dưới đây là một hàm permutaion dễ hiểu cho cả chuỗi và số nguyên làm đầu vào. Với điều này, bạn thậm chí có thể đặt độ dài đầu ra của mình (trong trường hợp bình thường, nó bằng với độ dài đầu vào)

Chuỗi

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

và đối với Integer, chỉ cần thay đổi phương thức người gọi và MakePermutations () vẫn chưa được xử lý:

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

ví dụ 1: GetAllPermutations ("abc", 3); "abc" "acb" "bac" "bca" "taxi" "cba"

ví dụ 2: GetAllPermutations ("abcd", 2); "ab" "ac" "quảng cáo" "ba" "bc" "bd" "ca" "cb" "cd" "da" "db" "dc" "

ví dụ 3: GetAllPermutations (486,2); 48 46 84 86 64 68


Tôi thích giải pháp của bạn vì điều này dễ hiểu, cảm ơn bạn vì điều đó! Tuy nhiên, tôi đã chọn đi với cái đó: stackoverflow.com/questions/756055/ ,. Lý do là ToList, ToArray và RemoveAt đều có độ phức tạp về thời gian là O (N). Vì vậy, về cơ bản, bạn phải xem qua tất cả các yếu tố của bộ sưu tập (xem stackoverflow.com/a/15042066/1132522 ). Tương tự cho int nơi bạn cơ bản đi qua tất cả các phần tử một lần nữa ở cuối để chuyển đổi chúng thành int. Tôi đồng ý rằng điều này không ảnh hưởng nhiều đến "abc" hoặc 486.
Andrew

2

Đây là chức năng sẽ in tất cả permutaion. Hàm này thực hiện logic Giải thích bằng 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

Dưới đây là thực hiện hoán vị của tôi. Đừng bận tâm đến các tên biến, vì tôi đã làm nó cho vui :)

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

Đây là một ví dụ cấp cao tôi đã viết để minh họa cho lời giải thích ngôn ngữ của con người mà Peter đã đưa ra:

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

Giải pháp này thực sự thiếu sót ở chỗ nếu bộ chuỗi chứa bất kỳ ký tự lặp lại nào, nó sẽ thất bại. Ví dụ: trên từ 'test', lệnh Ngoại trừ sẽ loại bỏ cả hai trường hợp 't' thay vì chỉ đầu tiên và cuối cùng khi cần thiết.
Middas

1
@Middas cũng phát hiện ra, may mắn là ôm đã đưa ra một giải pháp để giải quyết vấn đề này.
FBryant87

1

Nếu hiệu suất và bộ nhớ là một vấn đề, tôi đề nghị thực hiện rất hiệu quả này. Theo thuật toán của Heap trong Wikipedia , nó phải là nhanh nhất. Hy vọng nó sẽ phù hợp với nhu cầu của bạn :-)!

Cũng giống như so sánh điều này với việc thực hiện Linq cho 10! (bao gồm mã):

  • Đây là: 36288000 mặt hàng trong 235 millisecs
  • Linq: 36288000 mặt hàng trong 50051 millisec

    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

Đây là giải pháp của tôi trong JavaScript (NodeJS). Ý tưởng chính là chúng ta lấy một phần tử cùng một lúc, "loại bỏ nó" khỏi chuỗi, thay đổi phần còn lại của các ký tự và chèn phần tử ở phía trước.

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;

Và đây là các bài kiểm tra:

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

Đây là giải pháp đơn giản nhất tôi có thể nghĩ ra:

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

Các distributechức năng phải mất một yếu tố mới evà một ndanh sách -element và trả về một danh sách n+1liệt kê mỗi trong số đó đã echèn vào một nơi khác. Ví dụ: chèn 10tại một trong bốn vị trí có thể có trong danh sách [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]]

Các permutechức năng nếp gấp trên mỗi phần tử lần lượt phân phối trên các hoán vị tích lũy cho đến nay, mà đỉnh cao trong tất cả các hoán vị. Ví dụ: 6 hoán vị của danh sách [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]]

Thay đổi foldthành một scanđể giữ cho các bộ tích lũy trung gian làm sáng tỏ về cách các hoán vị được tạo ra một phần tử tại một thời điểm:

> 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

Liệt kê các hoán vị của một chuỗi. Tránh trùng lặp khi các ký tự được lặp lại:

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
Với rất nhiều giải pháp làm việc đã có mặt, bạn có thể muốn mô tả những gì làm cho giải pháp của bạn nổi bật so với tất cả các giải pháp khác ở đây.
nvoigt

Tránh trùng lặp khi các ký tự được lặp lại (bởi chindirala cho câu trả lời khác). Đối với "aab": aab aba baa
Val

1

Dựa trên giải pháp của @ Peter, đây là phiên bản khai báo Permutations()phương thức mở rộng kiểu LINQ đơn giản , hoạt động trên bất kỳ IEnumerable<T>.

Cách sử dụng (ví dụ về ký tự chuỗi):

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

Đầu ra:

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

Hoặc trên bất kỳ loại bộ sưu tập khác:

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

Đầu ra:

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

Đây là chức năng sẽ in tất cả các hoán vị đệ quy.

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

Đây là một câu trả lời C # được đơn giản hóa một chút.

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

Đầu ra:

1 2 3
1 3 2

2 1 3
2 3 1

3 2 1
3 1 2

0

Đây là giải pháp của tôi mà tôi dễ hiểu

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

Đây là một thực hiện thêm của thuật toán được đề cập.

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")đầu rastring[4] { "ab", "baa", "baa", "ab" }
Atomosk

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

Sẽ thật tuyệt nếu bạn có thể giải thích một chút về cách thức hoạt động của mã này, thay vì để nó ở đây một mình.
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);
        }
    }        
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.