Java 爪哇河内塔的时间分析

Java 爪哇河内塔的时间分析,java,data-structures,recursion,towers-of-hanoi,Java,Data Structures,Recursion,Towers Of Hanoi,我刚刚在Java中测试了towersofhanoi问题,并运行了以下代码: (为了方便起见,我已删除了sysout) 问题1:算法有指数增长(2^n-1),那么为什么我看不到从1到20的时间增长?但是突然从20-35跳了起来 问题2:另一件让我更加惊讶的事情是两人的时间相等。从19、(19,20)、(21,22)、(23,24)、(25,26)等开始。。。。有相似的时间。我无法理解这一点,如果算法的增长率确实是指数的,为什么两个指数给出了几乎相同的时间,然后在下一个指数上突然跳跃 注意:我重复了

我刚刚在Java中测试了towersofhanoi问题,并运行了以下代码: (为了方便起见,我已删除了
sysout

问题1:算法有指数增长(2^n-1),那么为什么我看不到从1到20的时间增长?但是突然从20-35跳了起来

问题2:另一件让我更加惊讶的事情是两人的时间相等。从19、(19,20)、(21,22)、(23,24)、(25,26)等开始。。。。有相似的时间。我无法理解这一点,如果算法的增长率确实是指数的,为什么两个指数给出了几乎相同的时间,然后在下一个指数上突然跳跃

注意:我重复了这个程序2-3次,获得了几乎相当的计时结果,因此您可以将其作为平均运行时间

编辑
尝试了
System.nanoTime()
,并获得以下结果:

Time taken for 1: 62644
Time taken for 2: 3500
Time taken for 3: 3500
Time taken for 4: 4200
Time taken for 5: 6300
Time taken for 6: 7350
Time taken for 7: 11549
Time taken for 8: 19948
Time taken for 9: 47245
Time taken for 10: 73142
Time taken for 11: 87491
Time taken for 12: 40246
Time taken for 13: 39196
Time taken for 14: 156784
Time taken for 15: 249875
Time taken for 16: 593541
Time taken for 17: 577092
Time taken for 18: 2318166
Time taken for 19: 2305217
Time taken for 20: 9468995
Time taken for 21: 9082284
Time taken for 22: 37747543
Time taken for 23: 37230646
Time taken for 24: 150416580
Time taken for 25: 145795297
Time taken for 26: 603730414
Time taken for 27: 578825875
Time taken for 28: 2409932558
Time taken for 29: 2399318129
Time taken for 30: 9777009489
输出几乎与毫秒相似,但它确实使画面清晰…回答我的问题1可能是,但问题2仍然很有趣。而
System.nanoTime()
又提出了一个问题:


问题3:为什么索引1比下一个索引(2,3…)等花费更多的时间?

当我们谈论程序的复杂性分析时,它意味着渐进地研究复杂性。就你的计时数据而言,你从中得出了指数增长和其他有趣的特征,如平等性,我可以简单地说,当你有一些数据,你盯着它看一段时间,你会得到有趣的模式:)

因此,从你的计时数据中得出的关于河内塔问题的理论复杂性的任何结论都是垃圾


然而,如果您对为什么代码实际显示出如此可观的指数增长感兴趣,那么这可能是由于java使用的RAM数量

答案1:在您开始播放18张光盘之前,您的计时测量太粗糙,无法显示任何有用的信息。在这个值范围内,就磁盘数量的运行时变化得出任何结论都是不明智的

回答2:由于河内塔问题已被大量研究,其时间复杂性众所周知,因此您不太可能想到一个具有更好时间复杂性的实现。因此,我们可能会得出这样的结论:在您的程序的连续运行中,有一些特定于您的实现的东西导致执行时间大致相等

我不能马上看出Java有什么问题,所以我会得出结论,您的系统返回的计时有点奇怪

编辑

纳米计时显示出与毫计时类似的结构。不过,我注意到,27个磁盘和28个磁盘的毫秒计时是相似的,然后是29个磁盘的大跳跃,而对于nano计时,则是从27个磁盘到28个磁盘的大跳跃,这与29个磁盘的计时相似

至于新问题3:我建议OP通过运行两次主循环来“加速”JIT和JVM,丢弃第一次运行的结果。我想我还应该在调用
System.out.println
之外获得结束时间,类似于:

long startTime = System.currentTimeMillis();
solveTowerOfHanoi(i, "A", "B", "C");
long endTime = System.currentTimeMillis();
System.out.println("Time taken for " + i + ": " + (endTime - startTime));
编辑2

针对OP的评论

对于1个磁盘比2个磁盘需要更长时间才能解决的问题,一个常见的解释是,您的计时包括幕后的“启动内容”——如果我正确地记得我的Java教育(我可能没有),Java编译器会创建字节码,而运行时系统会将字节码转换为机器代码。您可能(Java大师们在这里不厌其烦地表示蔑视)正在安排第二次翻译的时间。至于JIT,一般的方法是,只有当对代码的动态分析表明它应该启动时,即时编译器才会启动。它可能只在第二次循环时优化代码

编辑3

好的,我上钩,对代码的两个版本进行计时,第一个版本(下面第2列)与原始问题中的完全相同,第二个版本(下面第3列)使用
nanoTime
而不是
currentTimeMillis
。我的结论是

  • 正如我最初断言的,OP的系统有些可疑;我在第2列中显示的计时非常符合预期,因此这不是一般的Java问题
  • 在我的机器上实现
    nanoTime
    ,或者我对它的理解有严重问题。这增加了我最初主张背后的想法的分量,它很容易被计算机时钟误导,它们提供的结果不可能总是被视为表面价值
  • 在开始计时之前,我还测试了JIT/JVM的旋转,结果几乎没有影响,所以我没有在这里包含这些数据

    | Discs | milliS | nanoS      |
    |    1: |      0 |       7000 |
    |    2: |      0 |       4000 |
    |    3: |      0 |       6000 |
    |    4: |      0 |      11000 |
    |    5: |      0 |      22000 |
    |    6: |      0 |      43000 |
    |    7: |      0 |      84000 |
    |    8: |      0 |     172000 |
    |    9: |      1 |     331000 |
    |   10: |      0 |     663000 |
    |   11: |      2 |    1334000 |
    |   12: |      2 |    2632000 |
    |   13: |      6 |    5265000 |
    |   14: |     11 |   10476000 |
    |   15: |     21 |   22034000 |
    |   16: |     42 |   43407000 |
    |   17: |     85 |   89683000 |
    |   18: |    169 |  171209000 |
    |   19: |    337 | -655065000 |
    |   20: |    673 |  688203000 |
    |   21: |   1345 | -814465000 |
    |   22: |   2708 |  707742000 |
    |   23: |   5531 | -533716000 |
    |   24: |  10918 | -140615000 |
    |   25: |  21542 |  628928000 |
    |   26: |  42889 |   97892000 |
    |   27: |  85698 |  325197000 |
    |   28: | 172370 |  -80280000 |
    |   29: | 345650 |  110795000 |
    |   30: | 685006 |  453388000 |
    
    注:


    尝试使用
    System.nanoTime()
    而不是
    System.currentTimeMillis()
    来计算处理时间。@Crazenezz:使用
    System.nanoTime()
    并捕获结果,很高兴阅读结果。感谢您建议进行额外的研究。在for循环之外声明startTime变量。你总是会发现指数级的增长。@AlpeshPrajapati:这会总结每次迭代的时间,这不是我所需要的,这会给出错误的时间。你的系统返回的时间有些奇怪-我想你应该通过在你的系统上运行代码看到,你会得到类似的时间模式?我正在寻找一个JVM-RAM特定的答案。我建议OP通过运行两次主循环来“加速”JIT和JVM,丢弃第一次运行的结果-你能进一步解释吗?我将尝试在每个itration上分别反编译此代码,并查看优化如何处理此问题。好消息,谢谢。奇怪。您没有获得任何与之相同的配对计时
    long startTime = System.currentTimeMillis();
    solveTowerOfHanoi(i, "A", "B", "C");
    long endTime = System.currentTimeMillis();
    System.out.println("Time taken for " + i + ": " + (endTime - startTime));
    
    | Discs | milliS | nanoS      |
    |    1: |      0 |       7000 |
    |    2: |      0 |       4000 |
    |    3: |      0 |       6000 |
    |    4: |      0 |      11000 |
    |    5: |      0 |      22000 |
    |    6: |      0 |      43000 |
    |    7: |      0 |      84000 |
    |    8: |      0 |     172000 |
    |    9: |      1 |     331000 |
    |   10: |      0 |     663000 |
    |   11: |      2 |    1334000 |
    |   12: |      2 |    2632000 |
    |   13: |      6 |    5265000 |
    |   14: |     11 |   10476000 |
    |   15: |     21 |   22034000 |
    |   16: |     42 |   43407000 |
    |   17: |     85 |   89683000 |
    |   18: |    169 |  171209000 |
    |   19: |    337 | -655065000 |
    |   20: |    673 |  688203000 |
    |   21: |   1345 | -814465000 |
    |   22: |   2708 |  707742000 |
    |   23: |   5531 | -533716000 |
    |   24: |  10918 | -140615000 |
    |   25: |  21542 |  628928000 |
    |   26: |  42889 |   97892000 |
    |   27: |  85698 |  325197000 |
    |   28: | 172370 |  -80280000 |
    |   29: | 345650 |  110795000 |
    |   30: | 685006 |  453388000 |
    
    javac -v => Eclipse Java Compiler v_677_R32x, 3.2.1 release, Copyright IBM Corp 2000, 2006. All rights reserved.
    
    java --version => java version "1.4.2"
    gij (GNU libgcj) version 4.1.2 20071124 (Red Hat 4.1.2-42)