【问题标题】:Memoization of a Recursive Search递归搜索的记忆
【发布时间】:2015-05-03 14:28:04
【问题描述】:

我正在尝试解决一个问题,在该问题中,您必须计算给定特定参数可以制作的条码数量。我递归地解决了这个问题,每次都能得到正确的答案。但是,我的程序非常慢。我尝试使用我读过的一种称为 memoization 的技术来纠正这个问题,但是当给定某些输入(例如: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 我对递归搜索的记忆太慢了。救命!

【问题讨论】:

    标签: java recursion memoization


    【解决方案1】:

    您从记忆中排除所有来自count 调用单位的结果:

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

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

    要包含所有情况,您可以使用带有从 unitsbars 值生成的键的 HashMap。我使用了从 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”,对 count 的调用次数从超过 600 万次下降到 4,150 次,其中 415 个条目保存在 Map 中。

    【讨论】:

    • 我已经包含了 if 语句以避免我的数组出现 ArrayIndexOutOfBoundsException 错误。在你指出之后,我意识到对于 bar
    【解决方案2】:

    您的记忆实现看起来是有效的。这可能对一些人有所帮助,但这里真正的问题是您对算法的选择。

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

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

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

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

    【讨论】:

    • 你会建议什么样的组合?我不认为组合或排列计算会起作用,这是我熟悉的仅有的两种类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-12
    • 2012-09-27
    • 1970-01-01
    • 2018-09-08
    • 2014-05-03
    • 2012-11-26
    相关资源
    最近更新 更多