【问题标题】:Turning recursive function into tail recursion将递归函数变成尾递归
【发布时间】:2016-10-19 11:49:18
【问题描述】:

我在 ATS 中编码,并试图创建一个函数来查找给定整数的平方根。此处提供的代码完全符合我的要求,而不是尾递归。

implement intsqrt(n) = 
if(n >= 1)
  then let
    val n4 = n / 4
    val res = 2 * intsqrt(n4) + 1
  in
    if(res * res <= n) then res else 2*intsqrt(n4)
  end
  else n

我不确定其他人是否熟悉这种语言,但这是我使用它的第一周。我知道常规递归和尾递归之间的明显区别,但我只是不明白如何更改。

我什至不需要确切的代码来做到这一点,我只是想知道这怎么可能。为了让我找到 sqrt,我必须计算 n4 = 1 / n,然后将其乘以 2。但是,这样做会进入递归。我想要做的是计算一个结果,然后将它传递给我的下一个递归调用。

这是否意味着我需要以某种方式向后工作?希望这一切都有意义,但如果需要,我会尝试澄清。

谢谢!

【问题讨论】:

  • 你确定你的算法是正确的吗?我把它翻译成 Scheme,它适用于 4 和 16,但不适用于 9 和 25,除非我在翻译过程中出错。
  • @uselpa 据我所知是的。这是一个作业,提示是“对于每个 n >= 1,让 n4 = n / 4。然后 sqrt(n) = 2*sqrt(n4) 或 sqrt(n) = 2*sqrt(n4) + 1 。”即使它可能不适用于每个数字,您是否看到任何实用的方法将其转换为尾递归?
  • @uselpa 我得到了 3。据我所知,它完全按照它应该的方式工作。这更多的是关于如何构建我的代码。
  • 关于尾递归:看看所谓的“累积参数”。
  • 我发现了我的错误(n/4 是 ATS 中的整数除法)。我认为您不能以尾递归的方式实现这一点。你确定可以做到吗?

标签: function recursion tail-recursion ats


【解决方案1】:

在模式匹配“伪代码”(Haskell,其中: 是列表构建cons[] 一个空列表)中,您的函数是

isqrt n | n < 1 = n
        | res*res <= n = res
        | otherwise = 2 * isqrt(n `div` 4)
   where
        res = 2 * isqrt(n `div` 4) + 1

要将其转换为尾递归数据,我们必须自己维护相关数据,例如,在列表中。只需先向下迭代 n &lt; 1 案例,然后再向上迭代,直到模拟堆栈耗尽并可以产生最终结果。

转换很简单:

isqrt n = go n []
  where
    go n []     | n < 1 = n           -- initial special case
    go n (x:xs) | n < 1 = up n x xs   -- bottom reached - start going back up
    go n xs = go (n `div` 4) (n:xs)   -- no - continue still down

    up recres n (n1:ns) =             -- still some way to go up
        let res = 2*recres + 1
        in  if res*res <= n 
              then up res n1 ns       -- "return" new recursive-result
              else up (res-1) n1 ns   --   up the chain of previous "invocations"

    up recres n [] =                  -- the last leg 
        let res = 2*recres + 1
        in  if res*res <= n 
              then res                -- the final result
              else (res-1)

现在可以进一步简化代码。

【讨论】:

    【解决方案2】:

    执行此类操作的系统方法是通过 CPS 转换。 以下实现的特别之处在于,在调用 intsqrt_cps 期间分配的每个内存字节在调用返回后都会被释放。 这里没有 GC(与上面的 Haskell 解决方案不同):

    fun
    intsqrt_cps
    (
      n: int, k: int -<lincloptr1> int
    ) : int =
    if
    (n >= 1)
    then let
      val n4 = n / 4
    in
    //
    intsqrt_cps
    ( n4
    , llam(res) =>
      applin(k, if square(2*res+1) <= n then 2*res+1 else 2*res)
    ) (* intsqrt_cps *)
    //
    end // end of [then]
    else applin(k, 0) // end of [else]
    
    fun intsqrt(n:int): int = intsqrt_cps(n, llam(x) => x)
    

    完整的代码可以在以下位置找到:

    https://github.com/githwxi/ATS-Postiats/blob/master/doc/EXAMPLE/MISC/intsqrt_cps.dats

    您可以使用 valgrind 来验证是否存在内存泄漏:

    ==28857== Memcheck, a memory error detector
    ==28857== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==28857== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
    ==28857== Command: ./intsqrt_cps.exe
    ==28857==
    intsqrt(1023) = 31
    intsqrt(1024) = 32
    ==28857==
    ==28857== HEAP SUMMARY:
    ==28857==     in use at exit: 0 bytes in 0 blocks
    ==28857==   total heap usage: 14 allocs, 14 frees, 1,304 bytes allocated
    ==28857==
    ==28857== All heap blocks were freed -- no leaks are possible
    ==28857==
    ==28857== For counts of detected and suppressed errors, rerun with: -v
    ==28857== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    【讨论】:

      猜你喜欢
      • 2019-09-14
      • 1970-01-01
      • 2016-01-06
      • 2020-09-16
      • 2015-06-27
      • 1970-01-01
      • 2012-12-30
      • 1970-01-01
      • 2016-06-13
      相关资源
      最近更新 更多