Запоминание рекурсивной функции для игрового алгоритма? - PullRequest
0 голосов
/ 02 октября 2018

У меня есть рабочая рекурсивная функция для назначения.Тем не менее, часть требования состоит в том, чтобы снизить сложность, используя запоминание.

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

Например:

Levels: 0, 1, 2.
Cost of upgrades: 0, 5, 8.
Money per tap: 2, 4, 9.

Один из маршрутов с необходимой суммой $ 15:

  • Нажмите трижды, чтобы получить 2 х 3 доллара.(Осталось $ 6).
  • Обновление до $ 5 (Осталось $ 1.)
  • Нажмите дважды, чтобы получить $ 4 * 2 + $ 1.(Осталось $ 9).
  • Обновление до $ 8 (Осталось $ 1.)
  • Нажмите дважды, чтобы получить необходимую сумму ($ 19> $ 15.)

Всего 3+ 2 + 2 = 7 нажатий.

Более эффективный маршрут будет:

  • Нажмите четыре раза, чтобы получить 2 x 4 доллара.(Осталось 8 долларов.)
  • Обновление за 8 долларов, пропустите обновление второго уровня.(Осталось $ 0).

  • Нажмите дважды, чтобы получить необходимую сумму ($ 18> $ 15.)

Всего 4 + 2 = 6 нажатий.

Теперь у меня есть рабочая рекурсивная программа для вышеперечисленного.Я попытался запоминать программу, используя двумерный массив cache[levels][amount], где сохраняются значения для каждого уровня / количества и доступ к ним при необходимости.

Минимальное рабочее решение выглядит следующим образом:

import java.io.*;
import java.math.*;
import java.util.*;

class IdleGame {

  private static int fork(int current, int required, int level, int taps, int[] values, int[] costs, int maxLevel,
      int[][] cache) {
    int first = Integer.MAX_VALUE;
    int second = Integer.MAX_VALUE;

    if (current >= required) {
      return taps;
    }
    //upgrade path, call recursively if available.
        for (int i = level + 1; i <= maxLevel; i++) {
  if (current >= costs[i]) {
    if (cache[i][current] == 0) {
      first = fork(current - costs[i], required, i, taps, values, costs, maxLevel, cache);
      cache[i][current] = first;
    } else {
      // System.out.println("First");
      first = cache[i][current];
    }
  }
}

if (cache[level][current] == 0) {
  second = fork(current + values[level], required, level, taps + 1, values, costs, maxLevel, cache);
  cache[level][current] = second;
} else {
  // System.out.println("Second");
  second = cache[level][current]--;
}

    return first < second ? first : second;
  };

  public static void main(String[] args) {
      int[] values = {2,4,9};
      int[] costs = {0,5,8};
      int[][] cache = new int[3][16];
      int solution = fork(0, 15, 0, 0, values, costs, 2, cache);
          System.out.println(solution);
    }
}

Однако это решение не достаточно быстрое для моего тестового примера.Насколько я могу судить, я использую запомненные значения, когда есть другой экземпляр рекурсивной функции, вызывающей тот же level/value, это происходит, когда две рекурсивные функции пересекаются, т.е. имеют одинаковые параметры.

Любое руководствобудет с благодарностью.

1 Ответ

0 голосов
/ 02 октября 2018

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

private static int fork(int current, int required, int level, int taps, int[] values, int[] costs, int maxLevel, int[][] cache) {

    if (current >= required)
        return taps;

    // check cache, calculate value only if cache is empty
    if (cache[level][current] == 0) {

        // calculate first value
        int first = Integer.MAX_VALUE;
        for (int i = level + 1; i <= maxLevel; i++) {
            if (current >= costs[i]) {
                first = fork(current - costs[i], required, i, taps, values, costs, maxLevel, cache);
            }
        }

        // calculate second value
        int second = fork(current + values[level], required, level, taps + 1, values, costs, maxLevel, cache);

        // store result in cache
        cache[level][current] = first < second ? first : second;
    }

    // return cached value
    return cache[level][current];
}

Кстати, проверка, установлен ли кэш, путем проверки, если значение равно 0, не является хорошей идеей вгенеральный.Что если действительный результат на самом деле может быть 0?Лучше использовать обнуляемый тип или контейнер, который можно проверить на наличие или отсутствие ключа.

Еще одна вещь, которую я заметил, заключается в том, что в зависимости от ваших входных данных вы постоянно перезаписываете first в этом цикле, так каку вас нет условий перерыва.Таким образом, вы пересчитываете first для каждого costs[i], который меньше current.Обязательно найдите one costs[i], который вы хотите, и рассчитайте first только для этого.Если вы просто хотите найти первое costs[i] меньше current, просто добавьте break:

for (int i = level + 1; i <= maxLevel; i++) {
    if (current >= costs[i]) {
        first = fork(current - costs[i], required, i, taps, values, costs, maxLevel, cache);
        break;
    }
}

Если вы хотите найти самое маленькое costs[i] меньше current, вынужно сохранить индекс и вызвать fork после цикла:

// find smallest costs[i] smaller than current:
int smallestCostIndex = -1;
int smallestCost = Integer.MAX_VALUE;
for (int i = level + 1; i <= maxLevel; i++) {
    if (current >= costs[i] && costs[i] < smallestCost) {
        smallestCost = costs[i];
        smallestCostIndex = i;
    }
}
// calculate first using smallest costs[i] smaller than current (if it exists):
int first = Integer.MAX_VALUE;
if (smallestCostIndex >= 0) {
    first = fork(current - costs[i], required, i, taps, values, costs, maxLevel, cache);
}

===

Заметьте, что ваш код немного запутан и может использовать хороший рефакторинг для лучшегочитаемость.Например, вы можете создать класс для хранения параметров примитива и передать его экземпляр в fork.Также правильные коллекции, вероятно, лучше, чем простые массивы.А поскольку эти коллекции (массивы) всегда являются одним и тем же экземпляром (ами), вам не нужно передавать их в качестве параметров.Сделайте их членами вашего класса и сделайте fork нестатическим методом.Примерно так:

class GameParams {
    private int current;
    private int required;
    private int level;
    private int taps;

    // constructor, getters etc.
}

class GameState {
    private int value;
    private int cost;

    // constructor, getters etc.
}

class Game {

    private int maxLevel;                   // initialized to 2 in your case
    private List<GameState> states;         // initialized to {GameState(2,0), GameState(4,5), GameState(9,8)} in your case
    private Map<GameParams, int> cache;

    // constructor, getters etc.

    private int fork(GameParams params) {   // called with GameParams(0, 15, 0, 0)
        if (chache.contains(params))
            return cache.get(params);

        // ...
    }

}

Возьмите этот последний бит с крошкой соли, я просто напечатал его как некоторую форму руководства для ООП подхода к вашему коду.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...