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

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

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

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

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

4 2 6 10 15 50 

(где числа в этом случае соответствуют примеру, который я дал)

13 ответов

Решение

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

На самом деле, подумав об этом, это ILP, а значит и NP-hard.

Я бы предложил немного динамического программирования. По сути, вы бы определили значение "остаток" и установили его в соответствии с вашей целью (скажем, 50). Затем на каждом этапе вы будете делать следующее:

  1. Выясните, какая самая большая монета, которая может вписаться в остаток
  2. Подумайте, что произойдет, если вы (A) включите эту монету или (B) не включите эту монету.
  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;
}

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

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

В основном вам нужно решить следующее уравнение: 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 все возможные значения в зависимости от текущего значение г и тд.

Рекурсивное решение на основе ресурса алгоритмист.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)
}

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

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

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.

Это очень похоже на проблему с ранцем

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

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

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

Сортировать список в обратном порядке: [15 10 6 4 2]

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

Другие вопросы по тегам