正在为游戏算法记忆递归函数吗?

时间:2018-10-02 11:41:36

标签: java recursion memoization

我有一个有效的递归函数用于分配。但是,部分要求是通过使用备忘录降低复杂性。

该功能类似于移动点击游戏。用户可以执行a)点击b)升级。该功能找到最少的抽头次数以达到指定的数量。用户可以跳过级别-从而绕过该级别的费用。

例如:

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

其中一条路线,所需金额为$ 15:

  • 轻按三次,即可获得$ 2 x 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次抽头。

现在我有一个适用于上述情况的递归程序。我尝试通过使用2D数组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 :(得分:4)

我看不到为什么只存储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怎么办?最好使用可为null的类型或可以检查是否存在键的容器。

我注意到的另一件事是,由于您没有中断条件,因此根据您的输入,您将在该循环中反复覆盖first。因此,您为每个小于first的{​​{1}}重新计算costs[i]。您应该确保找到所需的 one current,并仅为此计算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

最后一点,我只是将其键入下来,作为某种形式的OOP方法指导您的代码。