Эта проблема требует некоторого простого рекурсивного недетерминированного программирования.
- Мы начинаем с определенной суммы и данного списка счетов номиналов , с неограниченными суммами каждого счета, по-видимому (в противном случае это была бы другая проблема).
- В каждый момент времени мы можем использовать самый большой счет или нет.
- Если мы его используем, общая сумма уменьшается на на сумму счета.
- Если итоговое значение равно 0, у нас есть решение!
- Если сумма отрицательна, она недействительна, поэтому мы должны отказаться от этого пути.
Код здесь будет следовать за другим моим ответом , который определяет общее количество решений (которые * больше, чем один, для вашего примера также). Нам просто нужно будет поддерживать и сами решения, тогда как код, упомянутый выше, учитывает их только.
Мы можем закодировать эту процедуру как рекурсивную процедуру обратного отслеживания с обратным вызовом, который вызывается для каждого успешно найденного решения:
(define (change sum bills callback)
(let loop ((sum sum) (sol '()) (bills bills)) ; "sol" for "solution"
(cond
((zero? sum) (callback sol))
((< sum 0) #f)
((null? bills) #f)
(else (apply
(lambda (b . bs)
(loop (- sum b) (cons b sol) bills) ; either use the first denomination,
(loop sum sol bs)) ; or backtrack, and don't!
bills)))))
Это должно быть вызвано одним из
(define (first-solution proc . params)
(call/cc (lambda (return)
(apply proc (append params
(list (lambda (sol)
(return sol))))))))
(define (n-solutions n proc . params) ; n assumed an integer
(let ([res '()]) ; n <= 0 gets ALL solutions
(call/cc (lambda (break)
(apply proc (append params
(list (lambda (sol)
(set! res (cons sol res))
(set! n (- n 1))
(cond ((zero? n) (break)))))))))
(reverse res)))
Тестирование
> (first-solution change 406 (list 100 10 5 2))
'(2 2 2 100 100 100 100)
> (n-solutions 7 change 415 (list 100 10 5 2 1))
'((5 10 100 100 100 100)
(1 2 2 10 100 100 100 100)
(1 1 1 2 10 100 100 100 100)
(1 1 1 1 1 10 100 100 100 100)
(5 5 5 100 100 100 100)
(1 2 2 5 5 100 100 100 100)
(1 1 1 2 5 5 100 100 100 100))
Относительно того, как этот код структурирован, ср. Как генерировать все перестановки элементов в списке по одному в Лиспе? Создает вложенные циклы с решением, доступным в теле самого внутреннего цикла.
Относительно того, как кодировать недетерминированный алгоритм (делая все возможные варианты сразу) правильным функциональным способом, см. Как сделать powerset в DrRacket? и Как найти разделы списка в схеме .