【问题标题】:How to tell if a number is a square number with recursion?如何判断一个数字是否是一个带有递归的平方数?
【发布时间】:2018-06-07 00:13:33
【问题描述】:

我解决了以下练习,但我不喜欢该解决方案:

使用递归编写函数 isPerfectSquare,以判断是否存在 Int 是一个完美的正方形 isPerfectSquare 1 -> 应该返回 True
isPerfectSquare 3 -> 应该返回 False

num+1 部分用于isPerfectSquare 0 and isPerfectSquare 1 的情况,这是我一点都不喜欢的部分之一,这是我的解决方案:

perfectSquare 0 1 = [0] ++ perfectSquare 1 3
perfectSquare current diff = [current] ++ perfectSquare (current + diff) (diff + 2)

isPerfectSquare num = any (==num) (take (num+1) (perfectSquare 0 1))

对于这个问题有什么更优雅的解决方案?当然我们不能使用 sqrt,也不能使用浮点运算。

【问题讨论】:

  • 平方是单调运算,所以你可以对它的逆进行二分搜索,这会比你这里的要快得多(而且很自然地递归)
  • 另外,为了激发你的胃口,perfectSquare 使用的模式实际上被称为 corecursive (但对于一个类而言,它会被视为递归当然)
  • @luqui 谢谢,我刚刚在下面写了评论

标签: haskell recursion


【解决方案1】:

@luqui 你的意思是这样吗?

pow n = n*n
perfectSquare pRoot pSquare | pow(pRoot) == pSquare = True
                            | pow(pRoot)>pSquare = perfectSquare (pRoot-1) pSquare
                            | otherwise = False
--
isPerfectSquare number = perfectSquare number number

我不敢相信我没有看到它 xD 非常感谢!我一定很累了

【讨论】:

  • 不,我说的是this algorithm(但使用函数代替数组),但我很高兴你得到了你满意的东西。
  • 您在上面递归实现的相同线性搜索也可以通过生成无限的正方形列表squares = [n*n | n <- [0..]] 并删除太小的正方形dropWhile (< number) squares 来执行。这会产生一个列表,其第一个元素是 number 并且仅当 number 是正方形时。 (不过,正如 luqui 所指出的,二分搜索更有效)
  • 您的原始解决方案可能比这个更好:它从 0 到平方根进行线性搜索,而这个从值到平方根进行线性搜索。所以你原来是 O(sqrt n) 运行时,渐近优于这个 O(n) 运行时。
【解决方案2】:

你可以对一些隐含的正方形列表执行某种“二分搜索”。然而,当然有一个问题,那就是我们首先需要一个上限。我们可以使用数字本身作为上限,因为对于所有整数平方,平方都大于我们平方的值。

所以它可能看起来像:

isPerfectSquare n = search 0 n
    where search i k | i > k = False
                     | j2 > n = search i (j-1)
                     | j2 < n = search (j+1) k
                     | otherwise = True
              where j = div (i+k) 2
                    j2 = j * j

为了验证一个数字 n 是一个完美的平方,因此我们有一个算法可以在 O(log n) 中运行,以防整数运算在常数中完成时间(例如,如果位数是固定的)。

【讨论】:

    【解决方案3】:

    Wikipedia suggests using Newton's method. 这是它的外观。我们将从一些样板文件开始。 ensure 是我经常使用的一个小组合器。它写得非常笼统,但我包含了一个简短的评论,应该可以很好地解释我们将如何使用它。

    import Control.Applicative
    import Control.Monad
    
    ensure :: Alternative f => (a -> Bool) -> a -> f a
    ensure p x = x <$ guard (p x)
    -- ensure p x | p x = Just x
    --            | otherwise = Nothing
    

    这是 Wikipedia 给出的公式的实现,用于在牛顿方法中迈出一步。 x 是我们目前对平方根的猜测,n 是我们取平方根的数字。

    stepApprox :: Integer -> Integer -> Integer
    stepApprox x n = (x + n `div` x) `div` 2
    

    现在我们可以递归调用这个步进函数,直到我们得到平方根的底。由于我们使用的是整数除法,因此正确的终止条件是观察近似值的下一步是否等于或大于当前步骤。这是唯一的递归函数。

    iterateStepApprox :: Integer -> Integer -> Integer
    iterateStepApprox x n = case x' - x of
        0 -> x
        1 -> x
        _ -> iterateStepApprox x' n
        where x' = stepApprox x n
    

    要将整个开发封装在一个漂亮的 API 中,要检查一个数字是否为正方形,我们只需检查其平方根的底是否与其平方。我们还需要选择一个起始近似值,但我们不必非常聪明——牛顿法很快就可以收敛到平方根。我们将选择一半的数字(四舍五入)作为我们的近似值。为避免除以零和其他废话,我们将零和负数设为特殊情况。

    isqrt :: Integer -> Maybe Integer
    isqrt n | n < 0 = Nothing
    isqrt 0 = Just 0
    isqrt n = ensure (\x -> x*x == n) (iterateStepApprox ((n+1)`div`2) n)
    

    现在我们完成了!即使对于大量数据,它也非常快:

    > :set +s
    > isqrt (10^10000) == Just (10^5000)
    True
    (0.58 secs, 182,610,408 bytes)
    

    你会花费比宇宙计算时间更长的时间。在我的测试中,它也比二进制搜索算法快一点。 (当然,not hand-rolling it yourself 还要快几个数量级,部分原因可能是它使用了better, but more complicated, algorithm based on Karatsuba multiplication。)

    【讨论】:

      【解决方案4】:

      如果函数是递归的,那么它是原始递归的,90% 的递归函数也是如此。对于这些folds 是快速有效的。考虑到程序员的时间,同时保持简单和正确很重要。

      现在,也就是说,挖掘 sqrt 等函数的文本模式可能会很有成效。 sqrt 返回一个浮点数。如果一个数字是一个完美的正方形,那么最后两个字符是“.0”。然而,该模式可能出现在任何尾数的开头。如果输入一个字符串,则相反,则为“0”。位于列表顶部。

      这个函数接受一个数字并返回一个布尔值

      fps n = (take 2.reverse.show $ (n / (sqrt n))) == "0."
      

      fps 10000.00001

      错误

      fps 10000
      

      是的

      【讨论】:

      • 为什么是n / sqrt n 而不仅仅是sqrt n?撇开所有与浮点一起出现的舍入问题不谈,即使sqrt 1e14 恰好是1e7,这也会在1e14 处出现,因为1e7 显示为1.0e7,而不是10000000.0。最好使用properFractionmod'或类似的方法来检查数字是否为整数。
      • 确实,n / sqrt n 是我的迭代过程中残留的。我在最终版本中掩盖了它,但在得到我想要的东西后忽略了它。缺少要求。示例为 1 和 3,即不大。问题更多的是如何递归地进行。没有具体说明什么。我无法让mod 适合。我的弱点。我的目标更趋于简单。
      猜你喜欢
      • 2021-11-25
      • 2018-01-16
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-01
      相关资源
      最近更新 更多