Teaser:您可以在不到一毫秒的时间内轻松执行此计算。详情如下...
哪个“更好”?
哪种算法“更好”的问题可能与执行时间有关,但也与其他事物有关,例如实现风格。
Staircase 实现更短、更简洁,恕我直言,更具可读性。更重要的是:它不涉及状态。您在此处引入的 c2 变量破坏了纯函数递归实现的优点(和美感)。这可能很容易解决,尽管实现已经变得与Staircase 更相似。
衡量绩效
关于执行时间的问题:在 Java 中正确测量执行时间是很棘手的。
相关阅读:
为了正确可靠地测量执行时间,有多种选择。除了像 VisualVM 这样的分析器之外,还有像 JMH 或 Caliper 这样的框架,但不可否认,使用它们可能需要一些努力。
对于非常基本的手动 Java Microbenchmark 的最简单形式,您必须考虑以下几点:
- 多次运行算法,让 JIT 有机会发挥作用
- 交替运行算法,而不是一个接一个地运行
- 随着输入大小的增加运行算法
- 以某种方式保存并打印计算结果,以防止计算被优化
- 不要在控制台在基准测试期间打印任何内容
- 考虑到时间可能被垃圾收集器 (GC) 扭曲
再次重申:这些只是经验法则,可能仍会出现意想不到的结果(有关详细信息,请参阅上面的链接)。但是通过这种策略,您通常可以很好地了解性能,并且至少可以看到算法之间是否存在真的显着差异。
方法之间的差异
Staircase 实现和Steps 实现没有太大区别。
主要的概念区别在于Staircase 实现是向下,而Steps 实现是向上。
实际影响性能的主要区别在于基本案例的处理方式(参见维基百科上的Recursion)。在您的实现中,您避免在不需要时递归调用该方法,代价是一些额外的if 语句。 Staircase 实现对基本情况进行了非常通用的处理,只需检查 n < 0 是否存在。
可以考虑结合两种方法的想法的“中间”解决方案:
class Staircase2
{
public static int counting(int n)
{
int result = 0;
if (n >= 1)
{
result += counting(n-1);
if (n >= 2)
{
result += counting(n-2);
if (n >= 3)
{
result += counting(n-3);
}
}
}
else
{
result += 1;
}
return result;
}
}
它仍然是没有状态的递归,并且总结了中间结果,通过使用一些if 查询避免了许多“无用”的调用。它已经明显快于最初的Staircase 实现,但仍然比Steps 实现慢一点。
为什么这两种解决方案都很慢
对于这两种实现,实际上没有什么需要计算的。该方法由少量if 语句和一些附加内容组成。这里最昂贵的东西实际上是递归本身,带有 deeeeply 嵌套调用树。
这就是这里的关键点:这是一个调用 tree。想象一下它为给定数量的步骤计算了什么,作为“伪代码调用层次结构”:
compute(5)
compute(4)
compute(3)
compute(2)
compute(1)
compute(0)
compute(0)
compute(1)
compute(0)
compute(0)
compute(2)
compute(1)
compute(0)
compute(0)
compute(1)
compute(0)
compute(3)
compute(2)
compute(1)
compute(0)
compute(0)
compute(1)
compute(0)
compute(0)
compute(2)
compute(1)
compute(0)
compute(0)
可以想象,当数字变大时,它会以指数方式增长。所有的结果都被计算了数百、数千或数百万次。这是可以避免的
快速解决方案
使计算更快的关键思想是使用Dynamic Programming。这基本上意味着中间结果被存储以供以后检索,因此不必一次又一次地计算它们。
在这个例子中实现了,也比较了所有方法的执行时间:
import java.util.Arrays;
public class StaircaseSteps
{
public static void main(String[] args)
{
for (int i = 5; i < 33; i++)
{
runStaircase(i);
runSteps(i);
runDynamic(i);
}
}
private static void runStaircase(int max)
{
long before = System.nanoTime();
long sum = 0;
for (int i = 0; i < max; i++)
{
sum += Staircase.counting(i);
}
long after = System.nanoTime();
System.out.println("Staircase up to "+max+" gives "+sum+" time "+(after-before)/1e6);
}
private static void runSteps(int max)
{
long before = System.nanoTime();
long sum = 0;
for (int i = 0; i < max; i++)
{
sum += Steps.step(i);
}
long after = System.nanoTime();
System.out.println("Steps up to "+max+" gives "+sum+" time "+(after-before)/1e6);
}
private static void runDynamic(int max)
{
long before = System.nanoTime();
long sum = 0;
for (int i = 0; i < max; i++)
{
sum += StaircaseDynamicProgramming.counting(i);
}
long after = System.nanoTime();
System.out.println("Dynamic up to "+max+" gives "+sum+" time "+(after-before)/1e6);
}
}
class Staircase
{
public static int counting(int n)
{
if (n < 0)
return 0;
else if (n == 0)
return 1;
else
return counting(n - 1) + counting(n - 2) + counting(n - 3);
}
}
class Steps
{
static int c2 = 0;
static int stairs;
public static int step(int c)
{
c2 = 0;
stairs = c;
return step2(0);
}
private static int step2(int c)
{
if (c + 1 < stairs)
{
if (c + 2 <= stairs)
{
if (c + 3 <= stairs)
{
step2(c + 3);
}
step2(c + 2);
}
step2(c + 1);
}
else
{
c2++;
}
return c2;
}
}
class StaircaseDynamicProgramming
{
public static int counting(int n)
{
int results[] = new int[n+1];
Arrays.fill(results, -1);
return counting(n, results);
}
private static int counting(int n, int results[])
{
int result = results[n];
if (result == -1)
{
result = 0;
if (n >= 1)
{
result += counting(n-1, results);
if (n >= 2)
{
result += counting(n-2, results);
if (n >= 3)
{
result += counting(n-3, results);
}
}
}
else
{
result += 1;
}
}
results[n] = result;
return result;
}
}
我的电脑上的结果如下:
...
Staircase up to 29 gives 34850335 time 310.672814
Steps up to 29 gives 34850335 time 112.237711
Dynamic up to 29 gives 34850335 time 0.089785
Staircase up to 30 gives 64099760 time 578.072582
Steps up to 30 gives 64099760 time 204.264142
Dynamic up to 30 gives 64099760 time 0.091524
Staircase up to 31 gives 117897840 time 1050.152703
Steps up to 31 gives 117897840 time 381.293274
Dynamic up to 31 gives 117897840 time 0.084565
Staircase up to 32 gives 216847936 time 1929.43348
Steps up to 32 gives 216847936 time 699.066728
Dynamic up to 32 gives 216847936 time 0.089089
语句顺序的细微变化(“微优化”)可能会产生很小的影响,或者会产生显着的差异。但是使用完全不同的方法可以产生真正的不同。