3
votes

Comment obtenir le moins de combinaison possible pour un problème de changement de pièce en C # en utilisant la récursivité

Je suis assez nouveau en C # et j'ai un problème de récursivité à résoudre. Je veux obtenir le moins de pièces possible dans ce problème de changement de pièce. J'y ai adapté un algorithme mais je dois renvoyer un objet de type Change qui contiendra le minimum de combinaison possible.

J'ai essayé d'implémenter un algorithme mais j'ai toutes les combinaisons possibles.

using System;
using System.Collections.Generic;
using System.Linq;

// Do not modify change
class Change
{
    public long coin2 = 0;
    public long bill5 = 0;
    public long bill10 = 0;
}

class Solution
{

    // Do not modify method signature
    public static Change OptimalChange(long s)
    {
        Change _change = new Change();
        //To implement here
        return _change;
    }

    public static int GetCombinations(int total, int index, int[] list, List<int> cur)
    {
        if (total == 0)
        {
            foreach (var i in cur)
            {
                Console.Write(i + " ");
            }
            Console.WriteLine();
            return 1;
        }
        if (index == list.Length)
        {
            return 0;
        }
        int ret = 0;
        for (; total >= 0; total -= list[index])
        {
            ret += GetCombinations(total, index + 1, list, cur);
            cur.Add(list[index]);

        }
        for (int i = 0; i < cur.Count(); i++)
        {
            while (cur.Count > i && cur[i] == list[index])
            {
                cur.RemoveAt(i);
            }
        }
        return ret;
    }

}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello Change");
        int s = 41;
        /*Change m = Solution.OptimalChange(s);
        Console.WriteLine("Coins(s) 2euros :" + m.coin2);
        Console.WriteLine("Bill(s) 5euors :" + m.bill5);
        Console.WriteLine("Bill(s) 10euors :" + m.bill10);

        long result = m.coin2*2 + m.bill5*5 + m.bill10*10;

        Console.WriteLine("\nChange given =", + result);*/
        Solution sol = new Solution();
        int[] l = {
            2,
            5,
            10
        };
        Solution.GetCombinations(s, 0, l, new List<int>());
    }
}

Résultat attendu

Exemple: Les pièces disponibles sont {2,5,10}

- J'ai une entrée de 12 -

Le programme devrait renvoyer

Pièce (s) 2euros: 1 Facture (s) 5eueurs: 0 Facture (s) 10eueurs: 1

- J'ai une entrée de 17 -

Le programme devrait rendre

Pièce (s) 2euros: 1 Projet de loi (s) 5eueurs: 1 Facture (s) 10eueurs: 1


10 commentaires

Est-il nécessaire d'utiliser la récursivité dans le devoir (scolaire)? Parce que je n'utiliserais pas la récursivité pour cela. Utilisez la récursivité lorsque les mêmes critères doivent être utilisés. Dans ce cas, vous voulez simplement essayer d'ajuster la valeur la plus élevée et utiliser le modulo pour vérifier les valeurs inférieures.


Pas nécessairement


C'est une étrange version de ce problème car vous ne dites pas quoi faire s'il n'y a pas de solution, ce qui est le cas pour 1 et 3.


Une fois que vous l'avez trouvé pour les pièces de valeur 2, 5 et 10, essayez les pièces de valeur 1, 7 et 15. Pour une entrée de 21, ce devrait être trois sept, pas un quinze et six unités.


Le problème est étiqueté «programmation dynamique»; pouvez-vous expliquer quelle technique de programmation dynamique vous utilisez?


@EricLippert Essayer de simplifier un problème compliqué en le décomposant en sous-problèmes plus simples, d'une manière récursive qui est utilisée.


Ok, aujourd'hui, c'est une bonne journée pour tes études. Commencez par: ce n'est pas de la programmation dynamique. La programmation dynamique est une technique de mémorisation souvent mais pas nécessairement utilisée pour améliorer les performances des algorithmes récursifs.


@EricLippert J'ai répondu à vos interrogations sur un autre post. Pour 1 et 3, il n'y a pas de solution. Il y a donc une solution pour chaque n> 3. Ma question porte sur la résolution de ce problème et non sur le concept de programmation dynamique, sinon j'aurais créé un post "Qu'est-ce que la programmation dynamique car Eric Lippert va le vérifier"


Je demande parce que c'est un problème sensé à résoudre avec la programmation dynamique . Je me demande si la question est "comment utiliser la programmation dynamique pour résoudre ce problème?" parce que vous ne semblez pas utiliser la programmation dynamique.


Cependant, vous ne devez pas commencer par la programmation dynamique. Commencez par créer une solution récursive correcte. Comprenez-vous la structure fondamentale d’une solution récursive? Commencez par: quel est votre cas de base? Pouvez-vous nous montrer comment vous implémenteriez OptimalChange pour le scénario de base?


3 Réponses :


2
votes

Ce que vous pourriez faire, c'est créer une méthode qui renvoie les dénominations qui constitueraient le montant. Vous pouvez le faire en faisant une boucle et trouver la plus grande dénomination inférieure ou égale au reste du montant. Vous faites cela jusqu'à ce que le reste soit inférieur à la valeur la plus basse disponible (2 euros). Quelque chose comme ceci:

10 2
2 1

Cela - pour 22 - renvoie 10, 10, 2. Vous pouvez ensuite utiliser la méthode LINQ GroupBy pour les regrouper en dénominations et écrire le décompte de chacune comme ceci :

    foreach (var d in MakeChange(22).GroupBy(i => i))
    {
        Console.WriteLine(d.Key + " " + d.Count());
    }

Cela imprimerait

public static IEnumerable<int> MakeChange(int amount)
{
    int[] denominations = {2, 5, 10};
    while (amount >= denominations.Min())
    {
        var denomination = denominations.Where(i => i <= amount).Max();
        amount -= denomination;
        yield return denomination;
    }
}

Cela signifie que vous auriez besoin de deux billets de 10 euros et d'une pièce de 2 euros pour faire 22 euros de monnaie.


4 commentaires

Merci Hans. Si j'ai 21 par exemple, la sortie du programme est 10 2, ce qui signifie 2 notes de 10. Cependant, c'est incorrect. Ce que je devrais obtenir, c'est 1 billet de 10, 1 billet de 5 et 3 pièces de 2: 10 + 5 + 6. Comment cela peut-il être modifié? Merci encore


@TropicalViking où sont les règles selon lesquelles il n'y a qu'une seule note sur 10? Si telle est la règle, les dénominations doivent être une liste, et quand une est utilisée, vous devez la supprimer de cette liste. Hans va réparer ça ;-)


@ J.vanLangen 10 peut être plusieurs fois, mais vous ne pouvez pas combiner le numéro 21 avec deux de 10 car nous n'avons pas de pièce de 1 cent


@ J.vanLangen, si montant = 1 ou 3, impossible. si montant = 10, alors 1 billet de 10, si montant = 13, alors 1 billet de 5 + 4 pièces de 2. J'espère que c'est clair.



2
votes

Ceci imprimera toutes les combinaisons possibles

2: 3
5: 1
10: 1

Si vous ne voulez que la moindre combinaison, changez le code pour ceci

static List<int> resultCoins = new List<int>();
    static void Main()
    {
        List<int> amounts = new List<int>() { 2, 5, 10 };
        Change(new List<int>(), amounts, 0, 0, 21);
        Display(resultCoins, amounts);
    }

    static void Change(List<int> coins, List<int> amounts, int highest, int sum, int goal)
    {
        if (sum == goal)
        {
            resultCoins = coins;
            return;
        }
        if (sum > goal)
        {
            return;
        }
        foreach (int value in amounts)
        {
            if (value >= highest)
            {
                List<int> copy = new List<int>(coins);
                copy.Add(value);
                Change(copy, amounts, value, sum + value, goal);
            }
        }
    }

    static void Display(List<int> coins, List<int> amounts)
    {
        foreach (int amount in amounts)
        {
            int count = coins.Count(value => value == amount);
            Console.WriteLine("{0}: {1}", amount, count);
        }
        Console.WriteLine();
    }

Résultat: p>

    static void Main()
    {
        List<int> coins = new List<int>();
        List<int> amounts = new List<int>() { 2, 5, 10 };
        //
        // Compute change for 21 cents.
        //
        Change(coins, amounts, 0, 0, 21);
    }

    static void Change(List<int> coins, List<int> amounts, int highest, int sum, int goal)
    {
        //
        // See if we are done.
        //
        if (sum == goal)
        {
            Display(coins, amounts);
            return;
        }
        //
        // See if we have too much.
        //
        if (sum > goal)
        {
            return;
        }
        //
        // Loop through amounts.
        //
        foreach (int value in amounts)
        {
            //
            // Only add higher or equal amounts.
            //
            if (value >= highest)
            {
                List<int> copy = new List<int>(coins);
                copy.Add(value);
                Change(copy, amounts, value, sum + value, goal);
            }
        }
    }

    static void Display(List<int> coins, List<int> amounts)
    {
        foreach (int amount in amounts)
        {
            int count = coins.Count(value => value == amount);
            Console.WriteLine("{0}: {1}",
                amount,
                count);
        }
        Console.WriteLine();
    }


2 commentaires

Votre code renvoyant Hello Change 2: 0 5: 0 10: 0 Par conséquent ne fonctionne pas. Pourquoi utiliser le plus élevé, la somme, etc. La signature de la méthode OptimalChange ne doit pas être modifiée. La solution est incorrecte, désolé.


@TropicalViking vérifie encore une fois, cela fonctionne vraiment. Vous pouvez prendre ma solution et la modifier facilement selon vos besoins



8
votes

Tout d'abord, comprenez quelle est l'idée de base de la récursivité:

  • Si nous pouvons résoudre le problème sans récursivité, résolvez-le et renvoyez la solution.
  • Si nous ne pouvons pas, réduisez le problème à un ou plusieurs problèmes plus petits, résolvez chaque problème plus petit de manière récursive et combinez les solutions pour résoudre le problème plus grand.

Deuxièmement, comprenez quelle est l'idée de base de la programmation dynamique:

  • Les solutions récursives recalculent souvent le même problème plusieurs fois; il est parfois plus efficace de stocker la solution que de la recalculer.

Très bien, attaquons votre problème.

public static Change OptimalChange(long s)
{
    if (s < 0) 
      return null;
    if (s == 0) 
      return new Change(0, 0, 0);
    Change tryTen = OptimalChange(s - 10)?.AddTen();
    ...

Pish tosh. Ce cours est terrible . Corrigez-le!

public static Change OptimalChange(long s)
{
    if (s < 0) 
      return null;
    if (s == 0) 
      return new Change(0, 0, 0);

Commencez avec une structure de données qui vous aide, pas une structure qui vous fait mal .

Maintenant, allons écrire une fonction récursive:

public static Change OptimalChange(long s)
{
    // Are we in the base case? Solve the problem.
    // If not, break it down into a smaller problem.
}

Quel est le cas de base? Il y en a en fait deux. Si la somme est négative, il n'y a pas de solution , et si la somme est nulle, alors il y a une solution nulle .

sealed class Change
{
    public long Two { get; }
    public long Five { get; }
    public long Ten { get; }
    public Change(long two, long five, long ten)
    {
      this.Two = two;
      this.Five = five;
      this.Ten = ten;
    }
    public Change AddTwo() => new Change(Two + 1, Five, Ten);
    public Change AddFive() => new Change(Two, Five + 1, Ten);
    public Change AddTen() => new Change(Two, Five, Ten + 1);
    public long Count => Two + Five + Ten;
    public long Total => Two * 2 + Five * 5 + Ten * 10;
}

Très bien, c'est la partie la plus facile. Quelle est la partie la plus difficile? Eh bien, s'il y a une solution, alors soit il a un deux, soit il a un cinq, soit il a un dix, non? Ou peut-être les trois. Voyons donc.

// Do not modify change
class Change
{
    public long coin2 = 0;
    public long bill5 = 0;
    public long bill10 = 0;
}

Pouvez-vous partir d'ici?

Voyez à quel point le problème devient plus simple lorsque vous avez une structure de données qui implémente les opérations dont vous avez besoin? Encore une fois créez toujours une structure de données qui vous aide .

Problème suivant: Cet algorithme est très inefficace . Pourquoi? Parce que nous refaisons constamment les problèmes que nous avons déjà posés. Supposons que nous évaluions OptimalChange (22). Cela appelle OptimalChange (12), qui appelle OptimalChange (7), qui appelle OptimalChange (5). Mais OptionalChange (12) appelle également OptimalChange (10), qui appelle à nouveau OptimalChange (5). La réponse n'a pas changé, mais nous refaisons le calcul.

Alors, que faisons-nous? Nous utilisons une technique de programmation dynamique . Il existe deux façons de procéder:

  • Continuez à être récursif et mémorisez la fonction récursive.
  • Créez un tableau de modifications et remplissez-le au fur et à mesure.

Mais attendez, il y a plus de problèmes que de problèmes de performances. Nous réduisons le problème d'un maximum de 10 et d'un minimum de 2 à chaque fois, mais le paramètre est long; cela pourrait être des milliards ou des billions. Si nous avons une solution récursive, nous allons faire exploser la pile, et si nous avons une solution basée sur un tableau, elle est trop grande.

Nous devons être plus intelligents pour résoudre ce problème sur le gamme d'entrées possibles . Pensez-y bien; Pouvons-nous résoudre le problème de manière analytique, sans récursivité, sans tableaux ni boucles de longue durée ? Ou, de manière équivalente, existe-t-il un moyen de réduire rapidement les problèmes extrêmement importants en petits problèmes ? Le petit problème peut alors être résolu avec la programmation dynamique.


Comme c'est toujours le cas avec les problèmes de devoirs rappelez-vous que vous êtes lié par les règles de la bonne recherche . Si vous utilisez des idées de SO dans vos solutions de devoirs, vous devez donner du crédit . Ne pas le faire est du plagiat et vous fera expulser d'une école décente si vous continuez.


1 commentaires

Merci @Eric Lippert, très apprécié. J'ai appris quelque chose de nouveau et très bien expliqué.