递归搜索的记忆

Memoization of a Recursive Search

我正在尝试解决一个问题,在该问题中,您必须计算在给定特定参数的情况下可以制作的可能条形码的数量。我递归地解决了这个问题,并且每次都能得到正确的答案。但是,我的程序非常慢。我尝试使用一种我读到的称为记忆化的技术来纠正此问题,但在给定特定输入(例如:10、10、10)时,我的程序仍然会爬行。这是 java.

中的代码

有人知道我做错了什么吗?

import java.util.Scanner;

//f(n, k, m) = sum (1 .. m) f(n - i, k - 1, m)

public class BarCode { public static int[][] memo;

public static int count(int units, int bars, int width) {
    int sum = 0;
    if (units >= 0 && memo[units][bars] != -1) //if the value has already been calculated return that value
            return memo[units][bars];

    for (int i = 1; i <= width; ++i) {
        if (units == 0 && bars == 0)
            return 1;
        else if (bars == 0)
            return 0;
        else {
            sum += count(units - i, bars - 1, width);
        }
    }
    if (units > -1)
        memo[units][bars] = sum;

    return sum;
}

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    //while (in.hasNext()) {
        int num = in.nextInt();
        int bars = in.nextInt();
        int width = in.nextInt();
        memo = new int[51][51];
        for (int i = 0; i < memo.length; ++i) {
            for (int j = 0; j < memo.length; ++j)
                memo[i][j] = -1;
        }
        int sum = 0;
        sum += count(num, bars, width);
        System.out.println(sum);
    //}
    in.close();
}
}

TL:DR 我对递归搜索的记忆太慢了。求助!

您的记忆化实施看起来是有效的。它可能对某些人有所帮助,但真正的问题在于您对算法的选择。

根据我对您的代码的粗略检查,对您的计数方法的调用平均会循环 width 次。每次循环时,它都会通过再次调用 count 来更深一层。看起来它还会从第一层向下循环 bars 层。如果我的几根苏格兰威士忌手指的渐近分析是正确的,这将导致一个具有 O(width^bars) 运行时复杂度的算法。随着您增加输入参数,尤其是条形,您的应用程序计算答案所需的步数将大大增加(在条形的情况下呈指数增长)。

您的记忆将减少所需的重复计算次数,但每个被记忆的值仍然需要至少计算一次以便记忆提供帮助。所以不管有没有记忆,你仍然在处理非多项式的时间复杂度,这总是意味着性能不佳。

您可能需要考虑寻找更有效的方法。与其尝试计算条形码组合的数量,不如尝试使用组合学来尝试计算它。例如,我可以尝试找出小写字符串的数量(仅使用字符 a-z)我可以通过生成所有小写字符串并计算它们的数量来生成长度为 n 的字符串,但这将有一个指数时间复杂,性能不佳。另一方面,我知道基本组合学告诉我,我可以创建的字符串数量的公式是 26^n(每个位置有 26 个选择,n 个位置),计算机可以轻松快速地对其进行计算。

寻找计算条形码数量的类似方法。

您从记忆中排除了 count 调用中 单位 < 0 的所有结果:

if (units > -1)
    memo[units][bars] = sum;

这会导致对这些值进行大量不必要的 count 调用。

要包括所有情况,您可以使用 HashMap,其键从 unitsbars 值生成。我使用了从 unitsbars 生成的字符串,如下所示:

//f(n, k, m) = sum (1 .. m) f(n - i, k - 1, m)

public class BarCode {
    public static Map<String, Integer>  memo    = new HashMap<>();

    public static int count(int units, int bars, int width) {
        int sum = 0;

        final String key = units + " " + bars;
        Integer memoSum = memo.get(key);
        if (memoSum != null) {
           return memoSum.intValue();
        }

        for (int i = 1; i <= width; ++i) {
            if (units == 0 && bars == 0)
                return 1;
            else if (bars == 0)
                return 0;
            else {
                sum += count(units - i, bars - 1, width);
            }
        }

        memo.put(key, Integer.valueOf(sum));

        return sum;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int num = in.nextInt();
        int bars = in.nextInt();
        int width = in.nextInt();
        memo = new HashMap<>();
        int sum = 0;
        sum += count(num, bars, width);
        System.out.println(sum);
        in.close();
    }
}

例如,对于输入值“10 10 10”,地图中保存了 415 个条目,count 的调用次数从超过 600 万次减少到 4,150 次。