【问题标题】:In java, will the program "remember" the result if you call the same function for the second time?在java中,如果你第二次调用同一个函数,程序会“记住”结果吗?
【发布时间】:2012-01-06 17:30:54
【问题描述】:

当我在考虑快速计算数字幂的算法时提出了这个问题,比如计算 x^n

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

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

那么程序返回power(x,n/2)的值后,是否会再次遍历整个递归过程来计算第二个power(x,n/2)的值?

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

【问题讨论】:

  • 依赖于编译器,而不是JVM本身,但我相信你没有指定是正确的。
  • 如果power 是不确定的会发生什么?
  • @Jeffrey - 那么记忆化将是无效的优化。
  • @Kevin:由于JVM是字节码的即时编译器,它也可以在JVM中完成。
  • 与其尝试优化低效的算法,我建议您只使用Math.pow(x, n) 使用这个和类似的方法来计算结果比维护结果缓存更快。 (而且更简单)

标签: java algorithm recursion memoization


【解决方案1】:

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

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

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

【讨论】:

  • 我删除了我的评论,以代替这里的更好答案,包括你的和@Kerrek 的。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    您想到的称为memoization:纯函数(即返回值仅取决于参数值的函数)可以记住它之前遇到的参数的结果。 p>

    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,因此 b7 = b1 × b2 × b4,我们只需要跟踪一个运行期限。另一个:5 = 101b,所以 b5 = b1 × 1 × b em>4.

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

    【讨论】:

    【解决方案4】:

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

    这就是你要问的吗?

    【讨论】:

      【解决方案5】:

      这是一道作业题吗?

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

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

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

      JVM 无法进行这种优化 - 您必须明确地进行。

      【讨论】:

      • 不,这不是硬件问题。我正在准备面试。所以你的第二个代码块是我应该做的?谢谢你的回答。
      【解决方案6】:

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

      但是,如果您的算法经常进行此类计算,则可能需要研究记忆化以缓存先前计算的结果。动态编程经常使用这个。

      【讨论】:

        【解决方案7】:

        这是许多递归解决方案的普遍问题。 (也适用于斐波那契数列)。

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

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

        【讨论】:

        • 在这种情况下,运行时间是 O(lgn) 而不是指数 :)
        • 啊!可悲的是没有。如果它只递归一次,是的。问题是它递归两次。并且每次递归也递归两次。因此,如果您将堆栈的深度视为t,它将执行2^t 计算。现在,再加上减半,正如你所说,lg(n) 这实际上是他函数的n 参数的线性。我应该更具体。 :)
        • 是的,你是对的,如果我们按照我在问题中写的内容进行操作,那么它是线性的。我真正的意思是如果我们以某种方式缓存第一次递归的结果。
        猜你喜欢
        • 2016-11-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多