Algorithm 哪个递归调用最有效?

Algorithm 哪个递归调用最有效?,algorithm,recursion,big-o,Algorithm,Recursion,Big O,因此,我有一个任务,要编写一个方法,借助递归求x的n次方。基本情况是,如果n=0,那么x^n=1。 如果n是奇数,那么x^n=x*(x^2)^((n-1)/2)。 如果n是偶数,那么x^n=(x^2)^(n/2)。不确定您是否需要此信息,但只是为了以防万一而添加 我的问题是,我有两种方法来使用递归调用,但我不确定哪一种是“最好的”还是最有效的 方法如下: public double findNthPowerOfXV2(int n, double x){ if(n == 0){

因此,我有一个任务,要编写一个方法,借助递归求x的n次方。基本情况是,如果n=0,那么x^n=1。 如果n是奇数,那么x^n=x*(x^2)^((n-1)/2)。 如果n是偶数,那么x^n=(x^2)^(n/2)。不确定您是否需要此信息,但只是为了以防万一而添加

我的问题是,我有两种方法来使用递归调用,但我不确定哪一种是“最好的”还是最有效的

方法如下:

public double findNthPowerOfXV2(int n, double x){
        if(n == 0){                                                                                     
            return 1;                                                                                   
        }else if(!(n % 2 == 0)){                                                                        
            return x * findNthPowerOfXV2(((n-1)/2), x) * findNthPowerOfXV2(((n-1)/2), x);               
            //return x * findNthPowerOfXV2(((n-1)/2), x * x) also works, but not sure which is best
        }else{

            return findNthPowerOfXV2(n/2, x) * findNthPowerOfXV2(n/2, x);
            //return findNthPowerOfXV2(n/2, x * x) also works, but not sure which is best
        }
}
Date start2 = new Date();
int rounds2 = 0;
double time2;
Date stop2;
do {
  main.findNthPowerOfXV2(5000, 1.001);
  stop2 = new Date();
  ++rounds2;
}while(stop2.getTime() - start2.getTime() < 1000);
time2 = (double) stop2.getTime() - start2.getTime() / rounds2;
System.out.println(time2);
在我看来,注释掉的代码行看起来更好、更正确。当我说注释掉的行使方法执行更少的循环时,我不确定我是否错了? 但是,当我测试完成该方法所需的时间时,未注释掉的行比注释掉的行快。即使它有两个递归调用,我猜这会导致更多的循环

这是我用来测试方法时间使用情况的代码:

public double findNthPowerOfXV2(int n, double x){
        if(n == 0){                                                                                     
            return 1;                                                                                   
        }else if(!(n % 2 == 0)){                                                                        
            return x * findNthPowerOfXV2(((n-1)/2), x) * findNthPowerOfXV2(((n-1)/2), x);               
            //return x * findNthPowerOfXV2(((n-1)/2), x * x) also works, but not sure which is best
        }else{

            return findNthPowerOfXV2(n/2, x) * findNthPowerOfXV2(n/2, x);
            //return findNthPowerOfXV2(n/2, x * x) also works, but not sure which is best
        }
}
Date start2 = new Date();
int rounds2 = 0;
double time2;
Date stop2;
do {
  main.findNthPowerOfXV2(5000, 1.001);
  stop2 = new Date();
  ++rounds2;
}while(stop2.getTime() - start2.getTime() < 1000);
time2 = (double) stop2.getTime() - start2.getTime() / rounds2;
System.out.println(time2);
datestart2=新日期();
int rounds2=0;
双时间2;
日期停止2;
做{
main.findNthPowerOfXV2(5000,1.001);
stop2=新日期();
++圆形2;
}while(stop2.getTime()-start2.getTime()<1000);
time2=(双精度)stop2.getTime()-start2.getTime()/rounds2;
System.out.println(time2);
当我使用未注释掉的行时,我得到
1.597890920672E12
当我使用注释掉的行时,我得到
1.597915039728E12


那么,哪一个更适合使用呢?我正在寻找一个解释,哪一个更好地使用和为什么。提前感谢:)

您认为注释掉的版本原则上更有效,这是正确的。考虑在计算<代码> 2 ^ 4 < /代码>时,我们做了多少个调用。 首先是低效的版本:

- 2^4
  - 2^2
    - 2^1
      - 2^0
      - 2^0
    - 2^1
      - 2^0
      - 2^0
  - 2^2
    - 2^1
      - 2^0
      - 2^0
    - 2^1
      - 2^0
      - 2^0
- 2^4
  - 2^2
    - 2^1
      - 2^0
如果我们加倍
n
,这个树的大小会加倍,所以这个算法是
O(n)
。另一种方法是假设它对于递归调用是正确的,然后证明它对于基调用也是正确的。在本例中,我们进行两个递归调用,每个递归调用执行
n/2
操作(根据假设),因此基本调用执行
2*n/2=n
操作

现在是高效版本:

- 2^4
  - 2^2
    - 2^1
      - 2^0
      - 2^0
    - 2^1
      - 2^0
      - 2^0
  - 2^2
    - 2^1
      - 2^0
      - 2^0
    - 2^1
      - 2^0
      - 2^0
- 2^4
  - 2^2
    - 2^1
      - 2^0
这里,如果我们将
n
的值加倍,树的大小将增加1,这就是
O(logn)

我将把严格的证据留给读者。在这两种情况下,我都忽略了
n
的奇数值,因为它们在大O方案中并不重要


您无法测量这种差异的事实可能更能说明您的基准测试代码:

  • 构造一个
    新日期
    对象调用内存管理器,可能触发垃圾回收器,并可能执行系统调用从操作系统获取当前时间,因此它可能比您正在测试的实际函数花费更长的时间

  • Java运行时(我假设这是Java)使用了一个即时编译器,它只在函数被多次调用时才启动,因此在它第一次运行时,结果并不具有代表性

  • 也许编译器足够聪明,可以认识到函数是纯函数,并且您正在使用相同的参数执行两个调用,因此它可能不像看上去那么低效


这些时间实际上是相同的;事实上,我希望相同算法的运行之间有更多的差异。您还可以测量构建一个
新日期()
所需的时间,该日期可能比您自己的函数大。更好的做法是进行固定次数的迭代,调整到只需几秒钟即可运行;然后重复多次,因为第一次代码可能还没有被JIT。标杆很难!