算法-二分法和牛顿法求指定精度平方根

本文探讨了如何使用二分法和牛顿法精确求解整数的平方根,包括精度检测、迭代优化与解决特定问题如完全平方和浮点数误差。通过实例展示了如何处理不同精度需求和特殊情况,如设置最大耐心次数并引入decimal模块提高精度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.引言

给定某个整数,给定固定精度,求该整数对应精度的平方根。

假设目标数字为 targrt,则寻找平方根数使得 sqrt(target) = Left = Right = sqrt(Left * Right)

退出条件: Left 与 Right 的整数部分和小数点后N(根据精度要求)位全部相同

二.精度状态检测

实现求平方根方法前首先需要定义一个检查精度的函数,用来检查对应平方根是否是满足精度的平方根。判断方法很简单,根据迭代得到的两个数字 Left 与 Right,去除整数部分,判断小数点后 N 位是否相等,如果满足N满足精度要求且全部相等,则说明满足退出条件。

1.参数说明

Left:平方根1

Right: 平方根2

Target(Int): 目标整数

Precision(Int): 平方根精度要求

2.判断精度

如果精度未达到指定 precision,则直接退出,不判断小数点位数是否相同

3.判断准度

如果精度达到 precision,则判断小数点后 precision 位是否相同,相同则满足退出条件

# 检查结果是否满足精度要求与平方根要求
def check_precision_state(left, right, target, precision):
    # 整数部分长度
    len_num = len(str(target))
    left = str(left)
    right = str(right)
    # 精度未满足要求
    if len(left) <= len_num + precision or len(right) <= len_num + precision:
        return False
    # 精度满足要求,判断小数点后位数是否相等
    for index in range(len_num, len_num + precision):
        if left[index] != right[index]:
            return False
    return True

三.二分法求对应精度平方根

1.思路

(1) 首先猜测一个初始值 init,如果 init * init = target 则直接退出,找到平方根

(2) 如果 init 不满足,则 Left = init ,Right = target / Left 

a.如果 Left < 真实平方根,则 Right > 真实平方根,因为两者乘积固定,所以取 (Left +  Right) / 2 会逼近真实平方根

b.同理,如果 Right < 真实平方根,则 Left > 真实平方根,所以取二者平均会逼近真实平方根

(3) 第二步循环迭代 Left 与 Right,直到二者达到精度检测状态的退出条件

2.实现

init 是随机给的启动值,按照上面的迭代求平均,当满足退出条件时打印 Left 或 Right 即可得到平方根。

# 二分查找法
def binary_sqrt_search(target, precision, init, max_patience=5):
    if init * init == target:
        print("精度要求: ", precision, " 目标开方数: ", target, " 平方根: ", init)
        return
    epoch = 0
    left = init
    right = target / left
    left = (left + right) / 2
    while not check_precision_state(left, right, target, precision):
        print("Epoch: ", epoch, " Left: ", right, " Right: ", left, " Dot: ", left * right)
        right = target / left
        left = (left + right) / 2
        epoch += 1

    print("精度要求: ", precision, " 目标开方数: ", target, " 平方根: ", str(left)[0: len(str(target)) + precision])

3.测试

    target_num = 15
    precision_need = 5
    init_num = 1
    binary_sqrt_search(target_num, precision_need, init_num)

可以看到每轮具体的 Left 与 Right 迭代过程,通过五轮迭代得到最终精度要求的平方根。 

四.牛顿法求对应精度平方根

1.思路

牛顿法通过导数逼近最值,本例是求目标数字的平方根,转换为数学语言是:

f(x) = x^2 - target,求 f(x) = 0 的根,f(x) 开口向上,有最小值,所以梯度下降求极值点。

导数为 : d(x) = 2 * x。

(1) 首先猜测一个初始值 init,如果 init * init = target 则直接退出,找到平方根

(2) 如果 init 不满足,则以 init - x0 为点做 f(x) 的切线,切线方程为:

           ​​                                f(x) = f{(x_0)}'(x - x_0) + f(x_0)

命令 f(x) = 0,求与 x 轴交点:

                                            x_1 = x_0 - \frac{f(x_0)}{​{f(x_0)}'}

与 x 轴的交点 x1 距离极值点更近,检查 Left = x1 与 Right = target / x1 是否满足退出条件

(3) 循环上面,直到 Left , Right 达到退出条件,迭代公式为:

                                            x_n = x_{n-1} - \frac{f(x_{n-1})}{​{f(x_{n-1})}'}

2.实现

根据迭代公式,不断迭代判断退出条件是否满足即可。

# 牛顿迭代
def newton_sqrt_search(target, precision, init, max_patience=5):
    if init * init == target:
        print("精度要求: ", precision, " 目标开方数: ", target_num, " 平方根: ", init)
        return

    # 原函数
    def f(x):
        return x * x - target

    # 原函数导数
    def d(x):
        return 2 * x

    epoch = 0
    while not check_precision_state(init, target / init, target, precision):
        print("Epoch: ", epoch, " Left: ", init, " Right: ", target / init, " Dot: ", target)
        init = init - f(init) / d(init)
        epoch += 1
        xn = target / init
    print("精度要求: ", precision, " 目标开方数: ", target, " 平方根: ", str(init)[0: len(str(target)) + precision])

3.测试

    target_num = 15
    precision_need = 5
    init_num = 1
    newton_sqrt_search(target_num, precision_need, init_num)

可以看到每轮具体的 Left 与 Right 迭代过程,通过五轮迭代得到最终精度要求的平方根。  

五.优化

1.当 target 可完全开平方时,精度无法达到要求,造成死循环

    target_num = 100
    precision_need = 10
    init_num = 1

设置如上参数时,可以看到很快就迭代到了 Left = Right = 10.0,但是由于未满足 precision = 10 的条件,程序出现了死循环 : 

解决方案:

如果 epoch 数超过 precision 的次数,且此时 Left = Right ,则程序退出循环,以牛顿法为例:

在 xn 更新迭代后加入如下退出条件,规避 target 可完全平方的问题。

        if epoch > precision and init == xn:
            break

可以看到,当迭代次数超过 precision = 10 次后,程序判断 Left = Right,所以退出:

2.浮点数问题 Left != Right,死循环

    target_num = 12345
    precision_need = 20
    init_num = 1

Left = 111.10805551354052, Right = 111.1080555135405 ,Left 和 Right 差了一位,且未达到精度要求,无法退出。

解决方案:

增加容忍度 patience 且判断 Left = Right 时,取二者长度短的位数进行 slice 判断,以二分法为例:

首先判断 Left 和 Right 中长度较短的一方,然后截断判断是否相等,如果超过 patience 次,二者都相同则退出。

# 二分查找法
def binary_sqrt_search(target, precision, init, max_patience=5):
    patience = 0

    if init * init == target:
        print("精度要求: ", precision, " 目标开方数: ", target, " 平方根: ", init)
        return
    epoch = 0
    left = init
    right = target / left
    left = (left + right) / 2
    while not check_precision_state(left, right, target, precision):
        print("Epoch: ", epoch, " Left: ", right, " Right: ", left, " Dot: ", left * right)
        right = target / left
        left = (left + right) / 2
        epoch += 1
        min_length = min(len(str(left)), len(str(right))) - 1
        if str(left)[:min_length] == str(right)[:min_length]:
            patience += 1
        if (epoch > precision and left == right) or patience >= max_patience:
            break
    print("精度要求: ", precision, " 目标开方数: ", target, " 平方根: ", str(left)[0: len(str(target)) + precision])

 添加 patience 参数后, Left,Right 长度差一位无法判断退出条件的问题解决。

3.如何做到任意精度

这个涉及到 python 规定的精度范围,默认得到的结果为 Float,可以导入 decimal 类 , 用 Decimal 的 getcontext().prec = precision_need 方法设置精度要求:

from decimal import *
getcontext().prec = 2000


# 牛顿迭代
def newton_sqrt_search(target, precision, init, max_patience=5):
    patience = 0

    if init * init == target:
        print("精度要求: ", precision, " 目标开方数: ", target_num, " 平方根: ", init)
        return

    # 原函数
    def f(x):
        return x * x - target

    # 原函数导数
    def d(x):
        return 2 * x

    epoch = 0
    while not check_precision_state(init, target / init, target, precision):
        print("Epoch: ", epoch, " Left: ", init, " Right: ", target / init, " Dot: ", target)
        init = Decimal(init - f(init) / d(init))
        epoch += 1
        xn = Decimal(target / init)
        min_length = min(len(str(init)), len(str(xn))) - 1
        if str(init)[:min_length] == str(xn)[:min_length]:
            patience += 1
        if (epoch > precision and init == xn) or patience >= max_patience:
            break
    print("精度要求: ", precision, " 目标开方数: ", target, " 平方根: ", str(init)[0: len(str(target)) + precision])

 两个算法都可以实现更高精度的逼近,对 12345 进行 2000 位精确的开方,数字太长了显示不出来了:

平方根: 111.10805551354051124500443874307524148991137745969772997648567316178259031751676291809207065226373835669370362811742152050650995528186453651088810300374124553024886011761731936119706928213607716328092361788989764904223342054475305540092633787224004646560942854443826531523681859165166096176773670230765094187754643932637254518970747506658681177079198245672440981179498050893855412083778835772179536269865912787226226736887770282085516567638758340633473300820321594099246086766452898854997321799296242105407926600042422702964779866537973537921952376083650172033945134793274716858780884135511564112850132148422655407148218142291101513684517224422270437556094095435154019876056848969745093294641401304108394983133414915566125346078122949890551958831325614828781511458353676837009537756704098033429391740699840838775569412451022298228113560091550966876076170862807143041332095850247041769259312108574288807111674705653242060330737920108209381818739548305879663743113565743426652004744571855136228507006772406145733498118351853237611767747406558178778292230731306967632605993390293539321805996517215376379981155830282469177758892478669631147464594052272649434100797857446148046891727193477117309461563200219395107799285258064960494034119987335877271254576502459161933211014017952330819487684084628134051418156703578979451252906542217777422754551748870252141135043280329622370765451777679730896221964194232623007682129863102326247351503860685657448838869773548413565971605273601202609528430246185017523924558238246579042497383122647986137273366629480742660830392541406365688980467558686507158321740719959496950274524446974658323472228606323830318476311798442071734544768315470086450734753759423099342200930948942135874388920284680039431903516091326029356587394642736782820083546907510046598790203323143985867745196793280827830711826045530631025712089745163706938594862519724633587389754319238237434599092390214049723656667007498055664437065067316372922963414401467647987830601583899221525440336359689069622
 

六.逻辑优化-后补

闲来无事又写了一次这个题,发现判断两数精度是否达到要求时有些复杂,原始是通过对比 left 和 right 的每一位都相同,也可以通过 mid ** 2 - target <= 0.00000000001 的方法判断精度,因此程序还可以继续优化。

n = 2

import math

def sqrt(st, end):
    mid = (st + end) / 2
    if abs(math.pow(mid, 2) - n) <= 0.0000000001:
        print(mid)
        return mid
    if math.pow(mid, 2) > n:
        sqrt(st, mid)
    else:
        sqrt(mid, end)

sqrt(1, 2)
1.4142135623842478

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BIT_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值