在java中,如果你第二次调用相同的函数,程序会“记住”结果吗?

时间:2011-11-26 02:14:48

标签: java algorithm recursion memoization

当我想到快速计算数字能力的算法时,就出现了这个问题,比如计算x^n

在Java中,递归部分类似于:

return power(x,n/2) * power(x,n/2);

所以在程序返回power(x,n/2)的值之后,它是否会再次通过整个递归过程来计算第二个power(x,n/2)的值?

如果是这样,我们应该先将值存储在某个变量中,然后返回该值的平方吗?

7 个答案:

答案 0 :(得分:13)

你所想到的是 memoization :一个纯函数(即一个返回值仅取决于参数值的函数)可以记住它之前遇到过的参数的结果。

Memoization由一些高级专用语言(如Maple)执行。但是,默认情况下,通用语言没有这种(相当复杂的)行为。

但是,在您的情况下,这不是必需的。您的算法不是使用递归,而是更好地实现为迭代。重复平方Binary exponentiation是标准算法。

这是一些类似C的伪代码(我的Java不是100%):

// compute pow(base, exponent)

int result = 1;
int term = base;

while (exponent != 0)
{
  if (exponent % 2 != 0) { result *= term; }
  term = term * term;
  exponent /= 2;
}

return result;

对于某些示例,7是二进制的111,因此 b 7 = b 1 × b 2 × b 4 ,我们只需要跟踪一个副本运行期限。另一个:5 = 101b,所以 b 5 = b 1 ×1× b 4

在C ++中,为任何将内存存储在哈希映射R f(T1, T2, ..., TN)中的函数std::unordered_map<std::tuple<T1, ..., TN>, R>构建一个通用的memoizing包装器非常容易。在调用时,包装器检查参数元组是否已经存在,如果是,则返回映射中的值,如果不是,则执行计算并将结果插入映射中。我确信类似的东西可以用Java编写。

答案 1 :(得分:3)

在非函数式语言中(函数可以(通常也有)产生副作用)编译器无法知道(或者至少很难知道)函数是否每次都会生成相同的值。

因此, power 的特定实现可能足以知道缓存特定幂函数的值。 (我怀疑这不太可能。)或者,java实现可能“只知道” power 以这种方式工作,并使用该知识做特殊的快捷方式。

否则,答案基本上是“否”。

答案 2 :(得分:1)

这是许多递归解决方案的一般问题。 (对于斐波那契系列,也是如此)。

通常情况下,您会使用称为“动态编程”的东西进行递归,但检查值是否在之前计算(“memoization”)。编译器会为你优化这一点是非常值得怀疑的。

我不同意goto10,因为你加倍称自己,计算时间会成倍增长,所以如果你真的必须使用这样的代码,那么它远非过早的优化。

答案 3 :(得分:1)

这是一个功课问题吗?

Java与大多数编程语言一样,默认情况下不会记忆方法调用的结果,因为方法调用可能会产生其他副作用。换句话说,JVM不能假设这两个语句是等价的:

 return power(x, n/2) * power(x, n/2)

 int sqrtPower = power(x, n/2);
 return sqrtPower * sqrtPower;

JVM无法进行此优化 - 您必须明确地执行此操作。

答案 4 :(得分:0)

通常,Java不会缓存方法调用的值。如果代码足够简单以便推理,JIT编译器可能会优化一些表达式,但在一般情况下不能这样做,因为可能存在副作用(另外,像Haskell这样的一些函数语言不允许这样做 - 效果可以避免这个问题。)

但是,如果您的算法经常进行这些类型的计算,您可能需要查看memoization以缓存先前计算的结果。动态编程经常使用它。

答案 5 :(得分:0)

将诸如那个的算术表达式的评估存储在堆栈中。有一个用于不同递归调用的堆栈,然后对于每个调用,都有一个堆栈用于评估函数内部的表达式。这是在评估的每个阶段“记住”部分结果的机制。

这就是你在问什么?

答案 6 :(得分:0)

你可以使用AOP来实现这一点。就这样,

  • 拦截对方法的调用
  • 如果可以根据缓存中的类,方法和参数值找到它,则返回结果。
  • 如果没有,继续调用并将返回值放在缓存中。
  • 返回结果。