递归算法效率 - Java

时间:2014-01-23 15:55:48

标签: java algorithm recursion performance

我正在编写一个编码项目,我对程序的速度有问题。该程序输入1到80之间的输入,此输入表示许多匹配杆,并输出可以使用该数量的匹配杆制作多少个不同的数字。

Ex:数字1可以用2根火柴棍形成,数字2需要5根火柴棒

以下是该计划的完整提示:

prompt of program

以下是我提出的用于计算所有可能输出的算法的代码,它对于输入的低端功能相当好,尽管对于大输入(如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);
            }
        }
    }

3 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

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

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的最大问题,您可能会在几毫秒内得到答案。

答案 2 :(得分:0)

您的问题是digitCounter的一次通话最多可以产生10次digitCounter来电,这可能会产生更多的电话。因此,您首次尝试优化将减少呼叫次数。

@pamphlet问:

  

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

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

使用n = 10调用digitCounter

对于count=0,您将在以下代码中结束(删除评论)

        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);

调用digitCounter(n - 5, count); digitCounter(5, count); n = 10会导致对digitCounter的额外10次调用。因此,通过调用一次而不是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) 。因此,您无需再次计算该值,从而为大n(例如n=80)节省了大量资金。即使缓存可能对小n没有帮助,甚至可能会增加运行时间(无论如何都要关心毫秒),你对大n有很大的好处(很容易达到秒/分钟)。