当我想到快速计算数字能力的算法时,就出现了这个问题,比如计算x^n
。
在Java中,递归部分类似于:
return power(x,n/2) * power(x,n/2);
所以在程序返回power(x,n/2)
的值之后,它是否会再次通过整个递归过程来计算第二个power(x,n/2)
的值?
如果是这样,我们应该先将值存储在某个变量中,然后返回该值的平方吗?
答案 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来实现这一点。就这样,