2010-09-20 9 views
4

J'ai une liste d'éléments qui ont des valeurs numériques et j'ai besoin de faire une somme en utilisant ces éléments. J'ai besoin de votre aide pour construire un tel algorithme. Ci-dessous, il est un exemple qui décrit mon problème, écrit en C#:Sélection d'éléments dans une liste pour obtenir une somme

int sum = 21; 

List<Item> list = new List<Item>(); 
list.Add(new Item() { Id = Guid.NewGuid(), Value = 3 }); 
list.Add(new Item() { Id = Guid.NewGuid(), Value = 5 }); 
list.Add(new Item() { Id = Guid.NewGuid(), Value = 12 }); 
list.Add(new Item() { Id = Guid.NewGuid(), Value = 3 }); 
list.Add(new Item() { Id = Guid.NewGuid(), Value = 2 }); 
list.Add(new Item() { Id = Guid.NewGuid(), Value = 7 }); 

List<Item> result = // the items in the list that has the defined sum. 

Note: Je n'ai aucune contrainte sur le nombre d'éléments dans le résultat.

+4

Ceci est NP-complet: C'est ce qu'on appelle le problème de somme de sous-ensemble. http://en.wikipedia.org/wiki/Subset_sum_problem. Vous ne trouverez malheureusement aucune solution * facile * - la meilleure solution trouvée à ce jour est 'O (2^(N/2))'. – Ani

+0

Les valeurs doivent-elles être les plus importantes parmi celles disponibles? –

+0

L'étiquette de devoirs est-elle manquante? –

Répondre

7

Ceci est appelé le Subset sum problem et est considéré comme un problème difficile dans la science informatique. Pas difficile comme dans difficile à faire, mais difficile à faire rapide - vous pouvez facilement écrire un algorithme pour le faire, mais pour les entrées importantes, il faudra facilement des milliards d'années.

Si vous êtes heureux avec une solution lente qui est seulement possible pour les petites entrées, essayer quelque chose comme ceci:

  • tous les sous-ensembles de Generate la liste d'entrée.

  • Pour chaque sous-ensemble, calculez la somme des éléments de ce sous-ensemble.

  • Renvoie le premier sous-ensemble pour lequel la somme correspond.

Voici une méthode qui retourne tous les sous-ensembles (en fait sousséquences car elle maintient l'ordre des éléments, bien que dans votre cas cela ne fait aucune différence):

/// <summary> 
/// Returns all subsequences of the input <see cref="IEnumerable&lt;T&gt;"/>. 
/// </summary> 
/// <param name="source">The sequence of items to generate 
/// subsequences of.</param> 
/// <returns>A collection containing all subsequences of the input 
/// <see cref="IEnumerable&lt;T&gt;"/>.</returns> 
public static IEnumerable<IEnumerable<T>> Subsequences<T>(
     this IEnumerable<T> source) 
{ 
    if (source == null) 
     throw new ArgumentNullException("source"); 
    // Ensure that the source IEnumerable is evaluated only once 
    return subsequences(source.ToArray()); 
} 

private static IEnumerable<IEnumerable<T>> subsequences<T>(IEnumerable<T> source) 
{ 
    if (source.Any()) 
    { 
     foreach (var comb in subsequences(source.Skip(1))) 
     { 
      yield return comb; 
      yield return source.Take(1).Concat(comb); 
     } 
    } 
    else 
    { 
     yield return Enumerable.Empty<T>(); 
    } 
} 

Vous pouvez maintenant écrire quelque chose comme ça ...

var result = list.Subsequences() 
       .FirstOrDefault(ss => ss.Sum(item => item.Value) == sum); 
+0

ne serait-il pas plus rapide dans la plupart des cas si vous calculez un sous-ensemble qui approche la somme dont vous avez besoin et que vous regardez s'il y a des éléments 1/2/3 qui correspondent à la marge dont vous avez besoin? Supposé que vous avez un grand nombre de chiffres et une idée de la taille des chiffres? Je demande ceci purement par intérêt :) – Samuel

+0

Peut être que nous devrions d'abord retirer les éléments qui ont une valeur supérieure à la somme :) J'aurai encore besoin d'optimisations mais la réponse était très explicative. Merci. –

+0

@Samuel, @Musa: Vous avez tous les deux trouvé une optimisation possible. Un peu plus et vous pourriez être sur votre chemin à la recherche en informatique! Si vous pouvez prouver que votre nouvel algorithme a une meilleure complexité que O (2^(n/2)) et publier cette preuve dans un article revu par des pairs, vous serez célèbre :) – Timwi

-1

Je ne sais pas exactement quel soleil vous êtes après ici. Si vous voulez que la somme de toutes les valeurs moissonneuses-batteuses, utilisez ce code:

int result = list.Sum(i => i.Value); 

Si vous voulez que tous les éléments qui a une valeur spécifique, utilisez ce code:

int x = 3; 
List<Item> result = list.Where(i => i.Value == x); 
+0

devrait être 'list.Where (i => i.Value == x);' (double signe égal pour l'égalité) –

+0

@Benny Jobigan Mais ce n'est pas ce que le questionneur veut même de toute façon – colithium

+0

Désolé pour l'erreur d'orthographe ici. Je l'ai réparé maintenant. –

1

récursive, ajouter des éléments jusqu'à ce que A) vous obtenez la somme ou B) vous obtenez trop, si A vous avez terminé, si B vous changez les éléments en essayant toutes les configurations possibles. Peut-être interdire le système d'ajouter un élément si l'élément courant est déjà plus grand que le dernier élément qui est allé la somme

2

ce qu'on appelle le problème de la somme sous-ensemble, avec la modification - que vous ne voulez pas arriver à zéro, mais à un particulier nombre.

Voici ce que Wiki a à dire à ce sujet - http://en.wikipedia.org/wiki/Subset_sum_problem.

Vous pouvez penser à certaines optimisations en fonction de vos connaissances du domaine. Par exemple, si le nombre le plus élevé + le nombre le plus bas est supérieur à la somme -> le nombre le plus élevé ne sera jamais utilisé, et vous pouvez l'exclure (et essayez la même chose pour le nouveau nombre le plus élevé ..). Je me souviens de l'avoir fait comme Samuel le suggérait - la manière récursive, qui n'était pas si terrible, (mais bien sûr, il y a toujours le problème de stackoverflow ...).