De problèmes NP-complets problème dans XKCD
Du problème/de la bande dessinée en question: http://xkcd.com/287/
Je ne suis pas sûr que c'est la meilleure façon de le faire, mais voici ce que j'ai trouvé jusqu'à présent. Je suis en utilisant CFML, mais il doit être lisible par n'importe qui.
<cffunction name="testCombo" returntype="boolean">
<cfargument name="currentCombo" type="string" required="true" />
<cfargument name="currentTotal" type="numeric" required="true" />
<cfargument name="apps" type="array" required="true" />
<cfset var a = 0 />
<cfset var found = false />
<cfloop from="1" to="#arrayLen(arguments.apps)#" index="a">
<cfset arguments.currentCombo = listAppend(arguments.currentCombo, arguments.apps[a].name) />
<cfset arguments.currentTotal = arguments.currentTotal + arguments.apps[a].cost />
<cfif arguments.currentTotal eq 15.05>
<!--- print current combo --->
<cfoutput><strong>#arguments.currentCombo# = 15.05</strong></cfoutput><br />
<cfreturn true />
<cfelseif arguments.currentTotal gt 15.05>
<cfoutput>#arguments.currentCombo# > 15.05 (aborting)</cfoutput><br />
<cfreturn false />
<cfelse>
<!--- less than 15.05 --->
<cfoutput>#arguments.currentCombo# < 15.05 (traversing)</cfoutput><br />
<cfset found = testCombo(arguments.currentCombo, arguments.currentTotal, arguments.apps) />
</cfif>
</cfloop>
</cffunction>
<cfset mf = {name="Mixed Fruit", cost=2.15} />
<cfset ff = {name="French Fries", cost=2.75} />
<cfset ss = {name="side salad", cost=3.35} />
<cfset hw = {name="hot wings", cost=3.55} />
<cfset ms = {name="moz sticks", cost=4.20} />
<cfset sp = {name="sampler plate", cost=5.80} />
<cfset apps = [ mf, ff, ss, hw, ms, sp ] />
<cfloop from="1" to="6" index="b">
<cfoutput>#testCombo(apps[b].name, apps[b].cost, apps)#</cfoutput>
</cfloop>
Le code ci-dessus me dit que la seule combinaison qui ajoute jusqu'à $15.05 est 7 ordres de Fruits Mélangés, et il prend 232 exécutions de mon testCombo fonction à remplir.
Est-il un meilleur algorithme pour venir à la bonne solution? Ai-je venir à la bonne solution?
- Belle. Vous aurez probablement fermé bien que la question n'est pas 🙁
- Il vous manque 1 échantillonneur, 2 hot wings, 1 salade de fruits.
- Oups, accidentellement laissé la question, j'ai l'intention de demander de sortir. Je l'ai ajouté. Merci!
- Que la langue est une abomination. Ses comme VB et XML a décidé d'avoir un bébé.
- Hein, juste de le résoudre par la force brute. 🙂 J'ai calculer une limite supérieure d'environ 1,715 combinaisons qui doivent être examinés.
- "Est-il un meilleur algorithme pour venir à la bonne solution?" - c'est l'une des plus grande question sans réponse dans l'informatique! Si stackoverflow arrive avec une solution générale, je vais être très impressionné 🙂
- L'enfant bâtard de deux bâtard de childs. +1 Paul Batum.
- Eh bien, vous pouvez dire tout simplement en regardant ce qu'il y a une meilleure solution que la force brutale - la réponse doit avoir un nombre impair de $?.?5 articles pour faire la fin total dans .05. Comment vous exprimer que dans le code en est une autre...
- Mais le problème dans la bande-dessinée n'est pas réellement le problème de sac-à-dos. C'est un entier problème de programmation. Cependant, il est aussi à la recherche d'Une solution, pas la combinaison optimale, donc je ne sais pas si c'est vraiment NP, car il n'est en fait pas sac à dos ou IP complète.
- Batum: Qui m'a donné un bon rire. Mes sentiments précisément.
- Il y a une langue qui ressemble vraiment à ça? *vomit*
Vous devez vous connecter pour publier un commentaire.
Le point sur un problème NP-complet problème n'est pas que c'est difficile sur un petit ensemble de données, mais que la quantité de travail à résoudre, il croît à un rythme supérieur à celui de polynôme, c'est à dire il n'y a pas de O(n^x) de l'algorithme.
Si le temps de la complexité est O(n!), comme dans (je crois) les deux problèmes mentionnés ci-dessus, qui est dans NP.
Il donne toutes les permutations des solutions, mais je pense que je vais battre tout le monde pour la taille du code.
Solution avec swiprolog:
N'est-il pas plus élégant avec la récursivité (en Perl)?
Sortie
marco@unimatrix-01:~$ ./xkcd-knapsack.pl
2.15, 2.15, 2.15, 2.15, 2.15, 2.15, 2.15
2.15, 3.55, 3.55, 5.8
2.15, 3.55, 5.8, 3.55
2.15, 5.8, 3.55, 3.55
3.55, 2.15, 3.55, 5.8
3.55, 2.15, 5.8, 3.55
3.55, 3.55, 2.15, 5.8
3.55, 5.8, 2.15, 3.55
5.8, 2.15, 3.55, 3.55
5.8, 3.55, 2.15, 3.55
Même si le sac à dos est un problème NP Complet, c'est un problème très particulier: l'habitude dynamique de il est en fait excellent (http://en.wikipedia.org/wiki/Knapsack_problem)
Et si vous faites la bonne analyse, il s'avère que c'est O(nW), n étant le nombre d'éléments, et W étant le numéro de la cible. Le problème, c'est quand vous avez de la DP sur un grand W, c'est quand nous arrivons à la NP comportement. Mais pour la plupart, sac-à-dos est relativement bien comportés et vous pouvez résoudre vraiment grandes instances de sans problèmes. Aussi loin que NP complet problèmes, sac à dos est l'un des plus faciles.
Voici la solution à l'aide de constraint.py
Donc la solution est de commander un échantillon de plaque, une salade de fruits, et de 2 ordres de hot wings, ou de l'ordre de 7 mixte de fruits.
Voici une solution avec F#:
Lire sur le Problème De Sac-À-Dos.
Vous avez toutes les bonnes combinaisons, mais vous êtes toujours en train de vérifier beaucoup plus que vous avez besoin d' (comme en témoigne le nombre de permutations de votre montre). Aussi, vous êtes en omettant le dernier élément qui frappe le 15.05 marque.
J'ai une version de PHP qui n'209 itérations de l'appel récursif (il n'2012 si je reçois toutes les permutations). Vous pouvez réduire votre compte si juste avant la fin de votre boucle, vous tirez sur l'élément que vous venez de vérifier.
Je ne sais pas CF syntaxe, mais ce sera quelque chose comme ceci:
EDIT: Pour référence, voici ma version de PHP:
Sortie
EDIT 2:
Depuis expliquant pourquoi vous pouvez supprimer les éléments prendra un petit plus que j'ai pu mettre dans un commentaire, je vais ajouter ici.
Fondamentalement, chaque récursion trouverez toutes les combinaisons comportant le actuellement à la recherche de l'élément (par exemple, la première étape sera de trouver de tout, y compris au moins une salade de fruits). La meilleure façon de le comprendre est de tracer l'exécution, mais depuis que va prendre beaucoup d'espace, je vais le faire comme si la cible était 6.45.
À ce stade, nous avons vérifié chaque combinaison qui comprend tout de la salade de fruits, il n'ya donc pas besoin de vérifier pour la salade de fruits à nouveau. Vous pouvez utiliser la même logique pour purger le tableau à chacun de la plus profonde récurrences ainsi.
Suivi par le biais de cette réalité a suggéré un autre léger gain de temps-la connaissance que les prix sont triés du plus faible au plus élevé signifie que nous n'avons pas besoin de garder le contrôle des éléments une fois que nous allons sur la cible.
Je voudrais faire une suggestion à propos de la conception de l'algorithme lui-même (qui est de savoir comment j'ai interprété l'intention de votre question initiale). Voici une fragment de la solution que j'ai écrit:
(Notez que le constructeur ne trier les éléments de menu en augmentant les prix, afin de permettre à la constante de temps de la résiliation anticipée lorsque le solde est plus petit que le reliquat de tout élément de menu.)
La sortie pour un exemple d'exécution est:
La conception suggestion, je tiens à souligner est que, dans le code ci-dessus, chaque imbriquée (récursif) appel de
findAndReportSolution(...)
traite de la quantité de exactement un élément de menu, identifié par leindex
argument. En d'autres termes, le récursive de nidification parallels le comportement d'un ensemble de boucles imbriquées; ultrapériphériques compte des utilisations possibles de la première option de menu, la prochaine en compte les usages du deuxième élément de menu, etc. (Sauf, bien sûr, l'utilisation de la récursivité libère le code de la dépendance sur un certain nombre d'éléments de menu!)Je suggère que cela la rend plus facile à concevoir le code, et plus facile à comprendre ce que chaque invocation est en train de faire (de la comptabilité pour toutes les utilisations possibles d'un élément spécifique, de déléguer le reste du menu, pour les actions subalternes à des appels). Il permet également d'éviter l'explosion combinatoire de la production de tous les arrangements de plusieurs élément de solution (comme dans la deuxième ligne de la au-dessus de la sortie, qui ne se produit qu'une fois, au lieu de plusieurs fois avec différentes ordre des éléments).
J'essaie d'optimiser l ' "évidence" du code, plutôt que d'essayer de minimiser le nombre d'appels d'une méthode spécifique. Par exemple, la conception ci-dessus permet à un délégué d'appel de déterminer si une solution a été atteint, plutôt que de l'emballage vérifier que le point de l'appel, qui permettrait de réduire le nombre d'appels au détriment d'encombrer le code.
Hmm, vous savez ce qui est bizarre. La solution est de sept du premier élément dans le menu.
Car ce n'était évidemment destiné à être résolu par le papier et crayon dans un court laps de temps, pourquoi ne pas diviser le total de la commande par le prix de chaque article pour voir si par hasard ils ont ordonné multiples d'un élément?
Par exemple,
15.05/2.15 = 7 fruits variés
15.05/2.75 = 5.5 frites.
Et de passer ensuite à des combinaisons simples...
15 /(2.15 + 2.75) = 3.06122449 fruits variés avec des frites.
En d'autres termes, supposons que la solution est censé être simple et résolubles par des humains sans accès à un ordinateur. Puis tester si le plus simple, le plus évident (et, par conséquent, hidden in plain sight) solution(s) œuvre(s).
Je vous jure que je suis en tirant la à la local de coney ce week-end lorsque je commande $4.77 valeur de hors-d'œuvre (y compris l'impôt) à 4:30 AM après la fermeture du club.
En python.
J'ai eu quelques problèmes avec "les variables globales" alors j'ai mis la fonction en tant que méthode d'un objet. Il est récursif et qu'il appelle lui-même 29 fois pour la question de la bande dessinée, s'arrêtant à la réussite de votre premier match
De sortie:
En fait, j'ai refait mon algorithme un peu plus. Il y avait plusieurs de corriger les combinaisons qui me manquait, et c'était dû au fait que j'étais de retour dès que le coût est allé sur 15.05 -- je n'étais pas la peine de vérifier d'autres (moins cher) des éléments que je pourrais ajouter. Voici mon nouvel algorithme:
De sortie:
Je pense que cela peut en avoir les bonnes combinaisons, mais ma question tient toujours: Est-il un meilleur algorithme?
Apprentissage de @rcar réponse, et un autre refactoring plus tard, j'ai la suite.
Comme tant de choses que je code, j'ai refait de CFML pour CFScript, mais le code est fondamentalement la même.
J'ai ajouté dans sa suggestion d'une dynamique de point de départ dans le tableau (au lieu de passer le tableau en valeur et en changeant sa valeur pour l'avenir récurrences), ce qui m'a amené à la même stats qu'il obtient (209 récurrences, 571 combinaison des vérifications de prix (itérations de boucle)), puis améliorés sur que en supposant que le tableau sera trié par type de coût, parce que c'est le cas, et la rupture dès que nous allons sur le prix cible. Avec la pause, nous en sommes à 209 récurrences et 376 itérations de boucle.
Ce que d'autres améliorations pourraient être apportées à l'algorithme?
Ici simultanées de mise en œuvre en Clojure. Pour calculer
(items-with-price 15.05)
prend environ 14 combinaison de génération de récurrences, et environ 10 possibilité de vérifications. M'a pris environ 6 minutes pour calculer le(items-with-price 100)
sur mon Intel Q9300.Cela ne donne que le premier a trouvé la réponse, ou
nil
si il n'y a aucune, c'est tout le problème des appels pour. Pourquoi faire plus de travail que vous avez dit de le faire 😉 ?Si vous voulez un algorithme optimisé, il est préférable d'essayer les prix dans l'ordre décroissant. Qui vous permet d'utiliser autant de la quantité restante d'abord et ensuite voir comment le reste peut être rempli en.
Aussi, vous pouvez utiliser les mathématiques pour déterminer la quantité maximale de chaque aliment au début de chaque temps de sorte que vous n'essayez pas de combinaisons qui irait de plus de $15.05 objectif.
Cet algorithme ne doit essayer 88 combinaisons afin d'obtenir une réponse complète et qui ressemble le plus bas qui a été posté ce jour:
Voici la sortie montrant les deux solutions: