Алгоритм определения комбинаций монет

недавно я столкнулся с запросом алгоритма программирования, для которого я понятия не имел, что делать. Я никогда раньше не писал алгоритм, так что я новичок в этом.

проблема сказала написать программу, чтобы определить все возможные комбинации монет для кассира, чтобы вернуть как изменение на основе значений монет и количества монет. Например, может быть валюта с 4 монетами: 2 цента, 6 центов, 10 центов и 15 центов. Сколько комбинаций этого это равно 50 центам?

язык, который я использую, - это C++, хотя это не имеет большого значения.

edit: это более конкретный вопрос программирования, но как бы я проанализировал строку в C++, чтобы получить значения монет? Они были даны в текстовом документе как

4 2 6 10 15 50 

(где числа в этом случае соответствуют приведенному мною примеру)

13 ответов


Это похоже на раздел, за исключением того, что вы не используете все целые числа в 1:50. Это также похоже на проблему упаковки бункера с небольшими различиями:

на самом деле, подумав об этом, он в МЛП, и таким образом NP-трудный.

Я бы предложил некоторое динамическое программирование appyroach. В принципе, вы бы определили значение "остаток" и установили его на любую вашу цель (скажем, 50). Затем на каждом шагу вы делали следующее:--3-->

  1. выяснить, что самая большая монета, которая может поместиться в пределах остатка
  2. подумайте, что произойдет, если вы (а) включили эту монету или (б) не включили эту монету.
  3. для каждого сценария, рекурсия.

Так что если остаток был 50, а самые большие монеты стоили 25 и 10, вы бы разделились на два сценария:

1. Remainder = 25, Coinset = 1x25
2. Remainder = 50, Coinset = 0x25

следующий шаг (для каждой ветви) может выглядеть так:

1-1. Remainder = 0,  Coinset = 2x25 <-- Note: Remainder=0 => Logged
1-2. Remainder = 25, Coinset = 1x25
2-1. Remainder = 40, Coinset = 0x25, 1x10
2-2. Remainder = 50, Coinset = 0x25, 0x10

каждая ветвь разделяется на две ветви, если:

  • остаток был 0 (в этом случае журнал)
  • остаток был меньше самой маленькой монеты (в этом случае вы бы сбросили ее)
  • больше не осталось монет (в этом случае вы бы отбросьте его с остатка != 0)

эта проблема хорошо известна как проблема изменения монет. Пожалуйста, проверьте этой и этой для сведения. Кроме того, если вы Google "монета" или "динамического программирования для монет", то вы получите много других полезных ресурсов.


вот рекурсивное решение на Java:

// Usage: int[] denoms = new int[] { 1, 2, 5, 10, 20, 50, 100, 200 };       
// System.out.println(ways(denoms, denoms.length, 200));
public static int ways(int denoms[], int index, int capacity) {
    if (capacity == 0) return 1;
    if (capacity < 0 || index <= 0 ) return 0;
    int withoutItem = ways(denoms, index - 1, capacity); 
    int withItem = ways(denoms, index, capacity - denoms[index - 1]); 
    return withoutItem + withItem;
}

Если у вас есть монеты 15, 10, 6 и 2 цента, и вам нужно найти, сколько различных способов добраться до 50 вы можете

  • посчитайте, сколько различных способов вы должны достичь 50 используя только 10, 6 и 2
  • посчитайте, сколько различных способов вы должны достичь 50-15, используя только 10, 6 и 2
  • посчитайте, сколько различных способов вы должны достичь 50-15*2, используя только 10, 6 и 2
  • посчитайте, сколько различных способов вы должны достичь 50-15*3, используя только 10, 6 и 2
  • суммируйте все эти результаты, которые, конечно, различны (в первом я не использовал монеты 15c, во втором я использовал один, в третьем два и в четвертом Три).

таким образом, вы в основном можете разделить проблему на меньшие проблемы (возможно, меньшее количество и меньше монет). Когда у вас есть только один тип монеты, Ответ, Конечно, тривиален (либо вы не можете достичь установленной суммы точно, либо вы можете единственным возможным способом).

более того, вы можно также избежать повторения тех же вычислений с помощью мемоизации, например количество способов достигнуть 20, используя только [6, 2] не зависят, если уже заплатил 30 были достигнуты через 15+15 или 10+10+10 так, в результате меньше проблем (20, [6, 2]) могут храниться и повторно использоваться.

в Python реализация этой идеи заключается в следующем

cache = {}

def howmany(amount, coins):
    prob = tuple([amount] + coins) # Problem signature
    if prob in cache:
        return cache[prob] # We computed this before
    if amount == 0:
        return 1 # It's always possible to give an exact change of 0 cents
    if len(coins) == 1:
        if amount % coins[0] == 0:
            return 1 # We can match prescribed amount with this coin
        else:
            return 0 # It's impossible
    total = 0
    n = 0
    while n * coins[0] <= amount:
        total += howmany(amount - n * coins[0], coins[1:])
        n += 1
    cache[prob] = total # Store in cache to avoid repeating this computation
    return total

print howmany(50, [15, 10, 6, 2])

что касается второй части вашего вопроса, предположим, что у вас есть эта строка в файле coins.txt:

#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
    std::ifstream coins_file("coins.txt");
    std::vector<int> coins;
    std::copy(std::istream_iterator<int>(coins_file),
              std::istream_iterator<int>(),
              std::back_inserter(coins));
}

Теперь вектор coins будет содержать возможные номиналы монет.


для такого небольшого количества монет, вы можете написать простое решение грубой силой.

что-то вроде этого:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

vector<int> v;

int solve(int total, int * coins, int lastI)
{
    if (total == 50) 
    {
        for (int i = 0; i < v.size(); i++)
        {
            cout << v.at(i) << ' ';
        }
        cout << "\n";
        return 1;
    }

    if (total > 50) return 0;

    int sum = 0;

    for (int i = lastI; i < 6; i++)
    {
        v.push_back(coins[i]);
        sum += solve(total + coins[i], coins, i); 
        v.pop_back();
    }

    return sum;
}


int main()
{
    int coins[6] = {2, 4, 6, 10, 15, 50};
    cout << solve(0, coins, 0) << endl;
}

очень грязное решение грубой силы, которое печатает все возможные комбинации.

Это очень известная проблема, поэтому попробуйте прочитать о лучших решениях, которые предоставили другие.


один довольно глупый подход заключается в следующем. Вы создаете отображение "монета со значением X используется y раз", а затем перечисляете все возможные комбинации и выбираете только те, которые составляют желаемую сумму. Очевидно, что для каждого значения X вы должны проверить Y в диапазоне от 0 до желаемой суммы. Это будет довольно медленно, но решит вашу задачу.


Это очень похоже на проблема с рюкзаком


вы в основном должны решить следующее уравнение: 50 = a * 4 + b * 6 + c * 10 + d * 15, где неизвестными являются a,b,c, d. Вы можете вычислить, например, d = (50 - a*4 - b*6 - c*10)/15 и так далее для каждой переменной. Затем вы начинаете давать d все возможные значения (вы должны начать с того, который имеет наименьшие возможные значения, здесь d): 0,1,2,3,4 и затем начать давать c все возможные значения в зависимости от текущего значения d и так далее.


сортировка списка назад: [15 10 6 4 2]

теперь решение для 50 ct может содержать 15 ct или нет. Таким образом, количество решений - это количество решений для 50 ct с использованием [10 6 4 2] (больше не учитывая монеты 15 ct) плюс количество решений для 35 ct (=50ct - 15ct) с использованием [15 10 6 4 2]. Повторите процесс для обеих подзадач.


алгоритм-это процедура решения проблемы, она не должна быть на каком-либо конкретном языке.

сначала разработайте входы:

typedef int CoinValue;

set<CoinValue> coinTypes;
int value;

и выходы:

set< map<CoinValue, int> > results;

решите для простейшего случая, который вы можете придумать первым:

coinTypes = { 1 }; // only one type of coin worth 1 cent
value = 51;

результат должен быть:

results = { [1 : 51] }; // only one solution, 51 - 1 cent coins

как бы вы решили вышеуказанное?

как насчет этого:

coinTypes = { 2 };
value = 51;

results = { }; // there is no solution

как насчет этого?

coinTypes = { 1, 2 };
value = { 4 };

results = { [2: 2], [2: 1, 1: 2], [1: 4] }; // the order I put the solutions in is a hint to how to do the algorithm.

рекурсивное решение на основеalgorithmist.com ресурс в Scala:

def countChange(money: Int, coins: List[Int]): Int = {
    if (money < 0 || coins.isEmpty) 0
    else if (money == 0) 1
    else countChange(money, coins.tail) + countChange(money - coins.head, coins)
}

другая версия Python:

def change(coins, money):
    return (
        change(coins[:-1], money) +
        change(coins, money - coins[-1])
        if money > 0 and coins
        else money == 0
    )