문자열 / 정수의 모든 순열 나열


159

인터뷰 프로그래밍 (일반적으로 인터뷰 경험이 아닌)의 일반적인 작업은 문자열 또는 정수를 사용하여 가능한 모든 순열을 나열하는 것입니다.

이것이 어떻게 수행되는지와 그러한 문제를 해결하는 논리의 예가 있습니까?

몇 가지 코드 스 니펫을 보았지만 주석 처리가 잘되지 않았으므로 따르기가 어렵습니다.


1
다음은 그래프를 포함하여 C #이 아닌 설명을 잘 설명 하는 순열에 대한 질문 입니다.
사용자 알 수 없음

답변:


152

우선 : 물론 재귀 냄새가납니다 !

당신은 또한 그 원리를 알고 싶었 기 때문에 나는 그것을 인간의 언어로 설명하기 위해 최선을 다했습니다. 나는 재귀가 대부분 매우 쉽다고 생각합니다. 두 단계 만 파악하면됩니다.

  1. 첫 번째 단계
  2. 다른 모든 단계 (모두 동일한 논리)

에서 인간의 언어 :

간단히 말해 :
1. 1 요소의 순열은 하나의 요소입니다.
2. 요소 집합의 순열은 각 요소의 목록이며 다른 요소의 모든 순열과 연결됩니다.

예:

세트에 하나의 요소가 있으면
반환하십시오.
파마 (a)-> a

집합에 두 개의 문자가있는 경우 : 각 요소에 대해 다음과 같이 나머지 요소의 순열을 추가하여 요소를 반환하십시오.

파마 (ab)->

a + 파마 (b)-> ab

b + 파마 (a)-> ba

또한 : 세트의 각 문자에 대해 :> 세트의 나머지로 연결된 문자를 반환합니다.

파마 (abc)->

a + perm (bc)-> abc , acb

b + 파마 (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
  }
}

씨#

OK, http://radio.weblogs.com/0111551/stories/2002/10/14/permutations.html 에서보다 정교한 (그리고 c # 태그가 붙어 있기 때문에) 다소 길지만 복사하기로 결정했습니다. 어쨌든 게시물은 원본에 의존하지 않습니다.

이 함수는 문자열을 가져와 정확한 문자열의 가능한 모든 순열을 기록합니다. 예를 들어 "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
두 번째 스왑 ( Swap(ref list[k], ref list[i]);)은 불필요합니다.
dance2die

1
이 솔루션에 감사드립니다. 나는이 바이올린 ( dotnetfiddle.net/oTzihw )을 (k와 m 대신 적절한 이름 지정으로) 만들었습니다 . 내가 알고있는 한 원래 배열을 제자리에서 편집하기 때문에 두 번째 스왑이 필요합니다 (역 추적).
Andrew

3
작은 점 : 스왑 방법이 임시 버퍼 변수로 구현되고 XOR ( dotnetperls.com/swap )을 사용하지 않는 것이 좋습니다
Sergioet

7
C # 7 튜플을 사용하면 스왑을 훨씬 더 우아하게 만들 수 있습니다.(a[x], a[y]) = (a[y], a[x]);
Darren

81

LINQ를 사용할 수 있으면 두 줄의 코드입니다. 여기에 내 답변을 참조 하십시오 .

편집하다

다음은 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
조합과 순열은 서로 다릅니다. 그것은 비슷하지만 귀하의 답변은 요소 집합의 모든 순열과는 다른 문제에 답하는 것 같습니다.
Shawn Kovac

@ShawnKovac, 이것을 지적 해 주셔서 감사합니다! 코드를 조합에서 순열로 업데이트했습니다.
Pengyang

1
@ Pengyang 나는 당신의 다른 대답을 보았는데 이것이 큰 도움이되었다고 말할 것입니다. 그러나 당신이 그것을 해결하는 올바른 방법을 지적했는지 알 수없는 또 다른 상황이 있습니다. 'HALLOWEEN'과 같은 단어의 모든 순열을 찾고 싶었지만 결과 집합에 'L'과 'E'를 모두 포함하고 싶다는 것을 알았습니다. 내 반복에서 각 반복 (길이 + +)으로 길이를 늘리는 방법을 반복하고 단어 HALLOWEEN (9 문자)의 전체 길이를 감안할 때 9 자 길이의 결과를 얻을 것으로 기대합니다 ...하지만 그렇지 않습니다 : 나는 단지 7을 얻는다 (1 L과 1 E는 생략 ​​됨)
MegaMark

또한 'H'가 단어에 한 번만 나타나는 9'H '를 보는 상황을 원하지 않는다는 점도 지적하고 싶습니다.
MegaMark 2009 년

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 # 코드 (스타일은 물론 개인적인 인식)로 구현하는 데 큰 도움이되었으므로 게시하면 게시하지 않을 것입니다. 당신을 위해).
Konrad Viltersten

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!-1O(N!)
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]);
  }
}

교체 기능을 작성하여 문자를 교체 할 수 있습니다.
이것은 permute (string, 0);


5
이것은 C #이 아닌 C처럼 보입니다.
Jon Schneider

9

우선, 집합에는 문자열이나 정수가 아닌 순열이 있으므로 "문자열의 문자 집합"을 의미한다고 가정하겠습니다.

크기 n의 집합에는 n이 있습니다! n 순열.

k = 1 ... n으로 호출 된 다음 의사 코드 (Wikipedia에서)! 모든 순열을 제공합니다.

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

다음은 동등한 파이썬 코드입니다 (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;.
Shawn Kovac

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())
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
미친 솔루션. 감사합니다!
Cristian E.

7

간단하기 때문에 FBryant87 접근 방식이 마음에 들었 습니다 . 불행히도, 그것은 많은 다른 "솔루션"처럼 모든 순열을 제공하지는 않습니다. 예를 들어 같은 숫자를 두 번 이상 포함하면 정수입니다. 656123을 예로 들어 보겠습니다. 라인 :

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

모든 발생의 원인이 제외됩니다 사용하여 제거 할, 즉 때 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을 처리하는 것을 발견했습니다.
Jack Casey


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 }

스왑을 변경하여 CLR 어레이의 가변 특성을 활용하여 성능을 크게 향상시킬 수 있지만이 구현은 소스 어레이와 관련하여 스레드로부터 안전하며 일부 상황에서는 바람직 할 수 있습니다. 또한 16 개 이상의 요소를 가진 배열의 경우, int32 오버플로가 발생하기 때문에 요소 17이 더 큰 / 임의 정밀도를 가진 유형으로 int를 대체해야합니다.


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

과에 대한 정수가 바로 호출 방법 변경 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에 큰 영향을 미치지 않는다는 것에 동의합니다.
Andrew

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

Peter가 말한 인간의 언어 설명 을 보여주는 내가 쓴 높은 수준의 예가 있습니다 .

    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

성능과 메모리가 문제가된다면이 효율적인 구현을 제안합니다. 위키 백과의 힙 알고리즘에 따르면 가장 빠릅니다. 그것이 당신의 필요에 맞길 바랍니다 :-)!

이것을 10의 Linq 구현과 비교하는 것처럼! (코드 포함) :

  • 235 밀리 세의 36288000 아이템
  • Linq : 50051 millisecs의 36288000 품목

    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함수는 새 요소 en-element 목록을 가져와 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로 변경하면 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

문자가 반복 될 때 복제를 피합니다 (다른 대답을 위해 chindirala에 의해). "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" }
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);

이 코드를 여기에 그대로 두지 않고이 코드의 작동 방식을 조금 더 자세히 설명 할 수 있다면 좋을 것입니다.
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);
        }
    }        
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.