【问题标题】:Recursive Algorithm Effiency - Java递归算法效率 - Java
【发布时间】:2014-01-23 15:55:47
【问题描述】:

我正在处理一个编码项目,但我遇到了程序速度问题。程序接受一个介于 1 到 80 之间的输入,这个输入代表一根火柴棒的数量,并输出用这个数量的火柴棒可以组成多少个不同的数字。

例如:数字 1 可以用 2 根火柴组成,数字 2 需要 5 根火柴

这是程序的完整提示:

这是我想出的用于计算所有可能结果的算法的代码,它在输入的低端运行得相当好,尽管它对于大型输入(例如需要花费数小时计算所有结果的 80 小时)变得非常低效可能性。我怎样才能将这个时间减少到最少?

n 表示输入,Counter 对象所做的只是跟踪创建的每个可能的数字

public static void digitCounter(int n, Counter count) {
    if (n < 2) {
        //ouput
    } else {
        if (count.getCount() == 0) {

            // If there are enough match sticks to form it
            // accounts for 0 only once
            if (n >= 7) {
                // counts 0
                count.setCount(10);
                // counts 1
                digitCounter(n - 2, count);
                // counts 2
                digitCounter(n - 5, count);
                // counts 3
                digitCounter(n - 5, count);
                // count 4
                digitCounter(n - 4, count);
                // counts 5
                digitCounter(n - 5, count);
                // counts 6
                digitCounter(n - 6, count);
                // counts 7
                digitCounter(n - 3, count);
                // counts 8
                digitCounter(n - 7, count);
                // counts 9
                digitCounter(n - 5, count);
            } else if (n == 6) {
                count.setCount(9);
                digitCounter(n - 2, count);
                digitCounter(n - 5, count);
                digitCounter(n - 5, count);
                digitCounter(n - 4, count);
                digitCounter(n - 5, count);
                digitCounter(n - 6, count);
                digitCounter(n - 3, count);
                digitCounter(n - 5, count);
            } else if (n == 5) {
                count.setCount(7);
                digitCounter(n - 2, count);
                digitCounter(n - 5, count);
                digitCounter(n - 5, count);
                digitCounter(n - 4, count);
                digitCounter(n - 5, count);
                digitCounter(n - 3, count);
                digitCounter(n - 5, count);
            } else if (n == 4) {
                count.setCount(3);
                digitCounter(n - 2, count);
                digitCounter(n - 4, count);
                digitCounter(n - 3, count);
            } else if (n == 3) {
                count.setCount(2);
                digitCounter(n - 2, count);
                digitCounter(n - 3, count);
            } else if (n == 2) {
                count.setCount(1);
                digitCounter(n - 2, count);
            }
        }

        // Accounts for every other number after 0 is accounted for so
        // numbers with leading 0's are not formed
        // Ex: 001 is illegal
        else {
            if (n >= 7) {
                count.setCount(count.getCount() + 10);
                digitCounter(n - 6, count);
                digitCounter(n - 2, count);
                digitCounter(n - 5, count);
                digitCounter(n - 5, count);
                digitCounter(n - 4, count);
                digitCounter(n - 5, count);
                digitCounter(n - 6, count);
                digitCounter(n - 3, count);
                digitCounter(n - 7, count);
                digitCounter(n - 5, count);
            } else if (n == 6) {
                count.setCount(count.getCount() + 9);
                digitCounter(n - 6, count);
                digitCounter(n - 2, count);
                digitCounter(n - 5, count);
                digitCounter(n - 5, count);
                digitCounter(n - 4, count);
                digitCounter(n - 5, count);
                digitCounter(n - 6, count);
                digitCounter(n - 3, count);
                digitCounter(n - 5, count);
            } else if (n == 5) {
                count.setCount(count.getCount() + 7);
                digitCounter(n - 2, count);
                digitCounter(n - 5, count);
                digitCounter(n - 5, count);
                digitCounter(n - 4, count);
                digitCounter(n - 5, count);
                digitCounter(n - 3, count);
                digitCounter(n - 5, count);
            } else if (n == 4) {
                count.setCount(count.getCount() + 3);
                digitCounter(n - 2, count);
                digitCounter(n - 4, count);
                digitCounter(n - 3, count);
            } else if (n == 3) {
                count.setCount(count.getCount() + 2);
                digitCounter(n - 2, count);
                digitCounter(n - 3, count);
            } else if (n == 2) {
                count.setCount(count.getCount() + 1);
                digitCounter(n - 2, count);
            }
        }
    }

【问题讨论】:

  • 您的许多案例多次致电digitCounter(n-5, count)。您是否希望第二次、第三次或第四次调用结果会有所不同?
  • 另外,我不明白你为什么使用count 而不是返回值。
  • 感谢您在来到 StackOverflow 之前花时间解决自己的问题。
  • 感谢大家的帮助!

标签: java algorithm recursion performance


【解决方案1】:

如果你修改你的设计,让digitCounter(n)返回可以由n 牙签组成的数字计数,然后将该值缓存在持久映射中怎么办?在进入digitCounter 时检查你的地图,如果你已经计算过一次,则返回缓存值。然后你仍然有一个递归算法,但它不需要对同一个 N 进行重复调用。

【讨论】:

    【解决方案2】:

    您可以将获得的解决方案数量乘以给定数量的匹配项。您还可以将解决方案的数量缓存到给定数量。

    public static long solutionsFor(int matchCount) {
        long[] counts = new long[matchCount+1];
        for(int i = 0; i <= matchCount; i++) {
           long count = ... calculate count based on previous values ...
           counts[i] = count;
        }
        return counts[matchCount];
    }
    

    性能提升是成本为 O(n),对于返回 long 的最大问题,您可能会在几毫秒内得到答案。

    【讨论】:

      【解决方案3】:

      您的问题是 digitCounter 的一次调用最多可以产生 10 次对 digitCounter 的调用,这可能会产生更多的调用。因此,您的第一次优化尝试是减少通话次数。

      @pamphlet 问:

      您的许多案例多次调用 digitCounter(n-5, count)。做 您希望第二次、第三次或第四次结果会有所不同 你叫它?

      这应该是您优化的第一条线索。每次调用digitCounter(n-5, count) 时,添加到计数器的实际计数应该相同。所以我建议你让数字计数器直接返回计数并自己添加。要表示是否计数为 0,请添加一个标志,指示它是否是对 digitCounter 的最顶层调用。

      致电digitCounter,n=10。

      对于count=0,您将得到以下代码(已删除cmets)

              if (n >= 7) {
                  count.setCount(10);
                  digitCounter(n - 2, count);
                  digitCounter(n - 5, count);
                  digitCounter(n - 5, count);
                  digitCounter(n - 4, count);
                  digitCounter(n - 5, count);
                  digitCounter(n - 6, count);
                  digitCounter(n - 3, count);
                  digitCounter(n - 7, count);
                  digitCounter(n - 5, count);
              } else if (n == 6) {
      

      你有效的调用

      1x digitCounter(n - 2, count);

      1x digitCounter(n - 3, count);

      1x digitCounter(n - 4, count);

      4x digitCounter(n - 5, count);

      1x digitCounter(n - 6, count);

      1x digitCounter(n - 7, count);

      在 n = 10 时调用 digitCounter(n - 5, count);(即 digitCounter(5, count);)会导致另外 10 次调用 digitCounter。因此,通过调用一次而不是 4 次,您可以节省 3x(1+10)=33 次调用。

      另一个优化是保存您执行的最后一次调用。你知道digitCounter(0) 的结果是 0。那你为什么要计算它呢?

              } else if (n == 2) {
                  count.setCount(count.getCount() + 1);
                  digitCounter(n - 2, count);
              }
      

      可以改写为

              } else if (n == 2) {
                  count.setCount(count.getCount() + 1);
                  digitCounter(2 - 2, count);
              }
      

      因此您可以删除调用 digitCounter(2 - 2, count);,因为它不会影响您的结果。同样,digitCounter(1) 的结果为 0。因此在优化案例 n=3 之后,我们又删除了 2 个不必要的调用。 digitCounter(2) 始终为 1。因此对于 n=4,您可以删除 3 个不必要的调用,只需将计数器增加 4 而不是 3。

      你也可以用同样的方法优化n=5digitCounter(3) 始终为 2。因此您可以消除 7 个额外的调用并将计数器增加 10 而不是仅 7。我让您自己优化 n=6 的情况。

      这应该已经大大减少了递归调用。它可能看起来很小,但它加起来很快。

      按照@JVMATL 的建议,下一个优化将是缓存,您可以为大 N 节省大量资金。对digitCounter(n-2) 的调用将导致对digitCounter((n-2) -2) 的调用,这相当于digitCounter(n-4)。因此,您无需再次计算该值,从而为 big n 节省大量资金(如 n=80)。尽管缓存对于小 n 可能无济于事,甚至可能会增加运行时间(无论如何都在乎毫秒),但对于大 n(很容易达到秒/分钟),您可以获得很大的收益。

      【讨论】:

        猜你喜欢
        • 2015-08-09
        • 1970-01-01
        • 2017-07-05
        • 1970-01-01
        • 2011-07-13
        • 1970-01-01
        • 1970-01-01
        • 2015-07-18
        • 2012-03-12
        相关资源
        最近更新 更多