Haskell中基于iterate实现牛顿-拉夫逊法:按误差阈值终止迭代并返回结果
解决Haskell牛顿-拉夫逊法的迭代终止问题
嘿,我完全懂你作为Haskell新手的困扰——用iterate生成无限迭代列表后,总靠手动take固定个数太不方便了。其实借助Haskell的惰性求值特性,我们可以轻松给算法加上误差阈值,让迭代自动终止在满足精度要求的结果上。下面给你两种实用的实现思路:
方案1:基于相邻迭代值的差判断收敛
这种方式是比较前后两次迭代结果的差值,当差值的绝对值小于你设定的margin时,就认为已经收敛到足够精确的结果了:
-- 先假设你已经定义了原函数f和它的导数g f :: Double -> Double f x = x^2 - 2 -- 举个例子:求sqrt(2)的根 g :: Double -> Double g x = 2 * x newtonUntil :: Double -> Double -> Double newtonUntil margin x0 = snd $ head $ dropWhile judgeError $ zip iterated (tail iterated) where -- 生成无限迭代序列(和你原来的逻辑一致) iterated = iterate step x0 step xn = xn - (f xn / g xn) -- 判断相邻两个迭代值的误差是否大于等于阈值 judgeError (prev, curr) = abs (curr - prev) >= margin
代码解释:
iterated就是你原来用iterate生成的无限迭代列表,Haskell的惰性求值不会预先计算所有元素,只会用到需要的部分。zip iterated (tail iterated)把每个迭代值和它的下一个值配对,得到类似[(x0,x1), (x1,x2), (x2,x3), ...]的序列。dropWhile judgeError会跳过所有误差不满足条件的配对,直到找到第一个差值小于margin的配对。snd $ head ...取出这个配对里的后一个值(也就是满足精度的迭代结果)。
测试一下:运行newtonUntil 0.0001 3.5,会返回约1.4142,也就是sqrt(2)的近似值,完全符合精度要求。
方案2:基于原函数值的绝对值判断收敛
如果你更关注当前迭代值代入原函数后是否足够接近0(毕竟牛顿法是求f(x)=0的根),可以用这种方式:
newtonUntil :: Double -> Double -> Double newtonUntil margin x0 = head $ dropWhile (\x -> abs (f x) >= margin) iterated where iterated = iterate step x0 step xn = xn - (f xn / g xn)
这个逻辑更直接:直接过滤掉所有|f(x)|大于等于margin的迭代值,第一个满足|f(x)| < margin的值就是我们要的结果。
进阶:更安全的版本(返回Maybe类型)
如果担心迭代不收敛的情况(虽然牛顿法通常收敛,但理论上存在不收敛的场景),可以用find函数返回Maybe Double类型,这样当迭代无法满足条件时会返回Nothing,避免程序出错:
import Data.List (find) newtonUntil :: Double -> Double -> Maybe Double newtonUntil margin x0 = snd <$> find judgeError (zip iterated (tail iterated)) where iterated = iterate step x0 step xn = xn - (f xn / g xn) judgeError (prev, curr) = abs (curr - prev) < margin
调用这个版本时,你可以用模式匹配处理结果,比如:
main = case newtonUntil 0.0001 3.5 of Just result -> print result Nothing -> putStrLn "迭代未收敛"
内容的提问来源于stack exchange,提问作者MasterGeekMX
相关产品推荐
相关产品推荐

