197 votes

Liste de toutes les permutations d'une chaîne de caractères/un nombre entier

Une tâche courante dans les entretiens de programmation (ce n'est pas mon expérience des entretiens) consiste à prendre une chaîne de caractères ou un nombre entier et à énumérer toutes les permutations possibles.

Existe-t-il un exemple de la manière de procéder et de la logique qui sous-tend la résolution d'un tel problème ?

J'ai vu quelques extraits de code mais ils n'étaient pas bien commentés/expliqués et donc difficiles à suivre.

1 votes

Voici une question sur les permutations avec quelques bonnes réponses explicatives, y compris un graphique mais pas en C#.

183voto

Peter Points 14647

Tout d'abord, ça sent comme récursion bien sûr !

Comme vous vouliez aussi connaître le principe, j'ai fait de mon mieux pour l'expliquer en langage humain. Je pense que la récursion est très facile la plupart du temps. Vous n'avez que deux étapes à saisir :

  1. La première étape
  2. Toutes les autres étapes (toutes avec la même logique)

Sur le langage humain :

En bref :

  1. La permutation d'un élément est un élément.

  2. La permutation d'un ensemble d'éléments est une liste de chacun des éléments, concaténée avec chaque permutation des autres éléments.

Exemple :

Si l'ensemble n'a qu'un seul élément -->
le rendre.
perm(a) -> a

Si l'ensemble a deux caractères : pour chaque élément de l'ensemble : retourner l'élément avec la permutation des éléments le reste des éléments, comme ceci :

perm(ab) ->

a + perm(b) -> ab

b + perm(a) -> ba

En outre : pour chaque caractère de l'ensemble : retour d'un caractère, concaténé avec une permutation du > reste de l'ensemble

perm(abc) ->

a + perm(bc) --> abc , acb

b + perm(ac) --> bac , bca

c + perm(ab) --> cabine , cba

perm(abc...z) -->

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

J'ai trouvé le pseudo-code sur 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#

OK, et quelque chose de plus élaboré (et puisque c'est étiqueté c #), de http://radio.weblogs.com/0111551/stories/2002/10/14/permutations.html : Plutôt long, mais j'ai décidé de le copier quand même, pour que le post ne soit pas dépendant de l'original.

La fonction prend une chaîne de caractères, et écrit toutes les permutations possibles de cette chaîne exacte, donc par exemple, si "ABC" a été fourni, devrait se déverser :

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

Code :

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

26 votes

Pour un peu plus de clarté, j'appellerais k "recursionDepth" et m "maxDepth".

3 votes

Le 2ème échange ( Swap(ref list[k], ref list[i]); ) n'est pas nécessaire.

3 votes

Merci pour cette solution. J'ai créé ce fiddle ( dotnetfiddle.net/oTzihw ) à partir de celui-ci (avec une dénomination appropriée au lieu de k et m). D'après ce que je comprends de l'algo, le deuxième Swap est nécessaire (pour revenir en arrière) puisque vous modifiez le tableau original sur place.

103voto

Pengyang Points 269

Il ne s'agit que de deux lignes de code si l'utilisation de LINQ est autorisée. Veuillez voir ma réponse ici .

EDIT

Voici ma fonction générique qui peut retourner toutes les permutations (pas les combinaisons) d'une liste de 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 }));
}

Exemple :

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

Sortie - une liste de listes d'entiers :

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

Comme cette fonction utilise LINQ, elle nécessite .net 3.5 ou plus.

3 votes

Les combinaisons et les permutations sont des choses différentes. c'est similaire, mais votre réponse semble répondre à un problème différent de celui de toutes les permutations d'un ensemble d'éléments.

0 votes

@ShawnKovac, merci de le signaler ! J'ai mis à jour mon code de combinaison à permutation.

1 votes

@Pengyang J'ai regardé votre autre réponse et je dirai que cela m'a beaucoup aidé mais j'ai une autre situation que je ne sais pas si vous avez indiqué la bonne façon de la résoudre. Je voulais trouver toutes les permutations d'un mot comme 'HALLOWEEN' mais j'ai découvert que je voulais également inclure les deux 'L' et les deux 'E' dans l'ensemble des résultats. Dans mes itérations, je boucle sur votre méthode en donnant une longueur accrue à chaque itération (longueur++) et je m'attendrais à ce que, étant donné la longueur totale du mot HALLOWEEN (9 caractères), j'obtienne des résultats de 9 caractères... mais ce n'est pas le cas : J'obtiens seulement 7 (1 L et 1 E sont omis)

47voto

user3277651 Points 51

J'ai trouvé la solution ici. Elle était écrite en Java, mais je l'ai convertie en C#. J'espère qu'elle vous aidera.

Enter image description here

Voici le code en 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 votes

C'est un portage d'une autre langue ? Définitivement +1 pour l'image, car elle apporte une réelle valeur ajoutée. Cependant, le code lui-même semble avoir un certain potentiel d'amélioration. Certaines parties mineures ne sont pas nécessaires, mais le plus important, c'est que je ressens ce sentiment C++ lorsque nous envoyons quelque chose et lui faisons quelque chose au lieu de fournir des paramètres et de récupérer une valeur retournée. En fait, j'ai utilisé votre image pour implémenter un code C# de style C# (le style étant ma perception personnelle, bien sûr), et cela m'a beaucoup aidé, donc quand je le posterai, je vous le volerai certainement (et vous créditerai pour cela).

0 votes

C# supporte le swapping comme Python depuis son introduction des tuples.

23voto

Najera Points 764

Récursion n'est pas nécessaire, ici est une bonne information sur cette solution.

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

J'utilise cet algorithme depuis des années, il a O(N) temps et espace complexité pour calculer chaque permutation .

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

0 votes

Fonctionne dès la sortie de la boîte !

1 votes

Peut-être que je ne comprends pas la notation O(n). Le N ne fait-il pas référence au nombre de "boucles internes" nécessaires pour que votre algorithme fonctionne ? Il me semble que si vous avez N nombres, il semble que c'est O(N * N !) (parce que N ! fois il doit faire N échanges). De plus, il doit faire une tonne de copies de tableaux. Ce code est "soigné", mais je ne l'utiliserais pas.

0 votes

@ericfrazer Chaque permutation n'utilise qu'une copie du tableau, et O(N-1) pour la séquence et O(N) pour les swaps, qui est O(N) . Et je l'utilise toujours en production mais avec une refactorisation pour générer une seule permutation comme : GetPermutation(i)0 <= i <= N!-1 . Je serai heureux d'utiliser quelque chose avec de meilleures performances que cela, alors n'hésitez pas à appeler une référence pour quelque chose de mieux, la plupart des utilisations alternatives. O(N!) en mémoire, vous pouvez donc vérifier cela aussi.

16voto

Meng Xue Points 281
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]);
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X