La théorie :
Si l'algorithme greedy produit toujours une réponse optimale pour un ensemble donné de pièces, on dit que cet ensemble est canonique .
Déclarer le le meilleur test algorithmique connu [O(n^3)] pour déterminer si un ensemble arbitraire de n pièces est canonique aussi succinctement que possible :
[c1,c2,..cn] is canonical iff for all w_ij |G(w_ij)| = |M(w_ij)|, 1 < i <= j <= n
où [c1,c2,...cn]
est la liste des dénominations de pièces triées par ordre décroissant avec cn = 1
G(x)
représente le résultat du vecteur de pièces de l'exécution de l'algorithme greedy sur l'entrée x
(retourné comme [a1, a2,..., an]
où ai
est le nombre de ci
)
M(x)
représente une représentation vectorielle de la pièce de monnaie x
qui utilise le moins de pièces
|V|
représente la taille du vecteur pièce V
: le nombre total de pièces dans le vecteur
et w_ij
est la valeur évaluée du vecteur pièce produit par G(c_(i-1) - 1)
après avoir incrémenté son j
par 1 et en mettant à zéro tous ses comptes de pièces à partir de j+1
a n
.
Mise en œuvre (JavaScript) :
/**
* Check if coins can be used greedily to optimally solve change-making problem
* coins: [c1, c2, c3...] : sorted descending with cn = 1
* return: [optimal?, minimalCounterExample | null, greedySubOptimal | null] */
function greedyIsOptimal(coins) {
for (let i = 1; i < coins.length; i++) {
greedyVector = makeChangeGreedy(coins, coins[i - 1] - 1)
for (let j = i; j < coins.length; j++) {
let [minimalCoins, w_ij] = getMinimalCoins(coins, j, greedyVector)
let greedyCoins = makeChangeGreedy(coins, w_ij)
if (coinCount(minimalCoins) < coinCount(greedyCoins))
return [false, minimalCoins, greedyCoins]
}
}
return [true, null, null]
}
// coins [c1, c2, c3...] sorted descending with cn = 1 => greedy coinVector for amount
function makeChangeGreedy(coins, amount) {
return coins.map(c => {
let numCoins = Math.floor(amount / c);
amount %= c
return numCoins;
})
}
// generate a potential counter-example in terms of its coinVector and total amount of change
function getMinimalCoins(coins, j, greedyVector) {
minimalCoins = greedyVector.slice();
minimalCoins[j - 1] += 1
for (let k = j; k < coins.length; k++) minimalCoins[k] = 0
return [minimalCoins, evaluateCoinVector(coins, minimalCoins)]
}
// return the total amount of change for coinVector
const evaluateCoinVector = (coins, coinVector) =>
coins.reduce((change, c, i) => change + c * coinVector[i], 0)
// return number of coins in coinVector
const coinCount = (coinVector) =>
coinVector.reduce((count, a) => count + a, 0)
/* Testing */
let someFailed = false;
function test(coins, expect) {
console.log(`testing ${coins}`)
let [optimal, minimal, greedy] = greedyIsOptimal(coins)
if (optimal != expect) (someFailed = true) && console.error(`expected optimal=${expect}
optimal: ${optimal}, amt:${evaluateCoinVector(coins, minimal)}, min: ${minimal}, greedy: ${greedy}`)
}
// canonical examples
test([25, 10, 5, 1], true) // USA
test([240, 60, 24, 12, 6, 3, 1], true) // Pound Sterling - 30
test([240, 60, 30, 12, 6, 3, 1], true) // Pound Sterling - 24
test([16, 8, 4, 2, 1], true) // Powers of 2
test([5, 3, 1], true) // Simple case
// non-canonical examples
test([240, 60, 30, 24, 12, 6, 3, 1], false) // Pound Sterling
test([25, 12, 10, 5, 1], false) // USA + 12c
test([25, 10, 1], false) // USA - nickel
test([4, 3, 1], false) // Simple cases
test([6, 5, 1], false)
console.log(someFailed ? "test(s) failed" : "All tests passed.")
3 votes
La réponse dépend beaucoup de ce que fait l'algorithme : il est facile d'être gourmand en pièces de monnaie, vous devriez nous dire ce que fait l'algorithme, et comment il le fait.
2 votes
... quel est le problème réel que l'algorithme est censé résoudre ?
2 votes
Ok, je suppose que je n'ai pas bien posé la question. Qu'en est-il d'un ensemble de dénominations qui doivent être vraies pour que l'algorithme ne fonctionne pas. Ex. faire 30 cents à partir de (25, 15, 1) greedy nous donne 25,1,1,1,1,1 mais 15 15 est mieux. Qu'est-ce qui fait que 25 15 et 1 empêchent l'algorithme de fonctionner ?
0 votes
Examinez s'ils forment une fr.wikipedia.org/wiki/Matroïde .
2 votes
C'est une bonne question, je ne sais pas pourquoi elle a été déclassée. Cette personne souhaite obtenir une explication sur les contraintes qu'un ensemble de pièces doit satisfaire pour que l'algorithme gourmand (qui sélectionne toujours la plus grande pièce possible) choisisse un nombre minimum de pièces pour payer un montant donné.
2 votes
@j_random_hacker On peut le déduire du commentaire de l'OP, mais pas de la question. La question elle-même ne contient aucun indice sur ce que l'algorithme est censé faire, et ce n'est donc pas une bonne question.
0 votes
@DanielFischer : C'est juste. C'est bien d'avoir corrigé la question.
1 votes
Je me posais la même question : pourquoi l'algorithme de changement de pièces fonctionne-t-il pour certains jeux de pièces ? Il existe un article qui donne une expression très laide sur la condition suffisante pour que la solution gourmande soit optimale. Croyez-moi, vous ne voulez pas lire cela :-)
1 votes
Je vote pour que cette question soit classée hors sujet car elle a sa place sur math.stackexchange.com.