[算法题][延伸][基础函数探究] 实现log函数

本文探讨了使用二分法和牛顿法求解对数函数log(x,base)的有效方法,介绍了在给定误差范围内找到对数值的算法实现,并对比了不同方法的效率。

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

算是Leetcode上求平方根的延伸版:
Leetcode 69. x的平方根


原始的 Sqrt(x)

描述:
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

原题有两种主要解法:

二分法
class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0 or x == 1:
            return x
        if x == 2:
            return 1
        i = 0
        j = x
        for idx in range(x):
            h = int((i+j)/2)
            r = h**2
            l = (h-1)**2
            if l >x:
                j = h
            if r < x:
                i = h
            if r == x:
                return int(h)
            if l<=x and r>x:
                return int(h-1)
牛顿法
class Solution:
    def mySqrt(self, x: int) -> int:
        if x<=1:
            return x
        cur = x/2
        thresh = 1e-5
        while True:
            pre = cur
            cur = (cur + x / cur) / 2
            if abs(cur - pre) < thresh:
                return int(cur)

所要实现的 log(x, base)

描述:
给定一个可允许的误差 error
给定 log ⁡ b x \log_bx logbx中的base和x
求在误差error范围内的log函数

思考:
那么不难看出,方法也大致符合上面两种方法,也用二分法和牛顿法就可以了,但是其实还有更多的方法来做,如泰勒级数算术几何平均近似,这里可以科学上网参考wiki 传送门

其中一种思路:
log ⁡ b x = g ( x ) \log_bx=g(x) logbx=g(x)
那么实际上要求:寻找一个 y y y,使得 g ( x ) − y ≤ error g(x)-y\le\text{error} g(x)yerror
那么由 log ⁡ b x = g ( x ) \log_bx=g(x) logbx=g(x) 可得 b g ( x ) = x b^{g(x)}=x bg(x)=x
也就是说寻找一个 y y y,使得 ∣ b y − x ∣ &lt; error | b^y-x|&lt;\text{error} byx<error

下面是两种主要解法:

二分法

最暴力的解法了,不过这个需要根据log函数的实际情况设置好初始最小值和最大值,避免幂运算的时候超出最大表示范围:

def log(x, base, error):
    """Need set minv maxv"""
    if x < 0:
        raise ValueError('X can not less than 0')
    if base < 0 or base == 1:
        raise ValueError('base can not less than 0 or equal 1')
    if x == 1:
        return 0
    
    minv = -1e4
    maxv = 10
    while True:
        mid = (minv + maxv) / 2
        r = base ** mid
        if abs(x - r) <= error:
            return mid
        if r > x:
            maxv = mid
        if r <= x:
            minv = mid
牛顿法

这里的牛顿法就要好很多了,不用给出最大值最小值的范围
原目标是寻找一个 y y y,使得 ∣ b y − x ∣ &lt; error | b^y-x|&lt;\text{error} byx<error
那么实际上可以认为我们需要求解方程:
f ( y ) = b y − x = 0 f(y)=b^y-x=0 f(y)=byx=0
根据牛顿法的公式:
x n + 1 = x n − f ( x ) f ′ ( x ) x_{n+1}=x_n - \frac{f(x)}{f&#x27;(x)} xn+1=xnf(x)f(x)
代入我们设定的符号,可得:
y n + 1 = y n + f ( y ) f ′ ( y ) y_{n+1} = y_n + \frac{f(y)}{f&#x27;(y)} yn+1=yn+f(y)f(y)
即:
y n + 1 = y n + b y n − x b y n ⋅ ln ⁡ b y_{n+1} = y_n + \frac{b^{y_n}-x}{b^{y_n}\cdot \ln b} yn+1=yn+bynlnbbynx
实际上还面临着 ln ⁡ b \ln b lnb的求解问题,这时为了节省运算时间我们让 ln ⁡ b = 1 \ln b=1 lnb=1
或者也可以采用 a n + 1 = a n + exp ⁡ ( a n ) − b exp ⁡ ( a n ) a_{n+1} = a_n + \frac{\exp(a_n)-b}{\exp(a_n)} an+1=an+exp(an)exp(an)b 的方式得出一个大致的 ln ⁡ b \ln b lnb,如误差在1e-2即可。

"""牛顿法1"""
def log(x, base, error):
    if x < 0:
        raise ValueError('X can not less than 0')
    if base < 0 or base == 1:
        raise ValueError('base can not less than 0 or equal 1')
    if x == 1:
        return 0

    y = 1
    while True:
        y_ = y
        y = y + (x - base ** y) / (base ** y)
        if abs(y - y_) < error:
            return y

对于 ln ⁡ x \ln x lnx的求法,wiki上给出了对 exp ⁡ ( y / 2 ) − x exp ⁡ ( y − 2 ) = 0 \exp(y/2)-x\exp(y-2)=0 exp(y/2)xexp(y2)=0进行优化,即求: y n + 1 = y n + 2 ⋅ x − exp ⁡ ( y n ) x + exp ⁡ ( y n ) y_{n+1} = y_n + 2\cdot\frac{x-\exp(y_n)}{x+\exp(y_n)} yn+1=yn+2x+exp(yn)xexp(yn)
仿照这个式子,下面有:

"""牛顿法2"""
def log(x, base, error):
    if x < 0:
        raise ValueError('X can not less than 0')
    if base < 0 or base == 1:
        raise ValueError('X can not less than 0')
    if x == 1:
        return 0

    y = 1
    while True:
        y_ = y
        y = y + 2 * (x - base ** y) / (x + base ** y)
        if abs(y - y_) < error:
            return y
比较
log(x=3, base=6, error=1e-5)
0.6131471927654585  # math.log 系统给出的方法
0.6131471623666584  # 二分法  error = 3.039880003274931e-08 loop=31
0.6131431996226185  # 牛顿法1 error = 4.269444931814803e-06 loop=44
0.6131514622103903  # 牛顿法2 error = 3.99314283994201e-06  loop=47

base更大的时候,循环次数loop差距会更大,但是几种方法的优劣差距不变

其他

求解 log ⁡ b x \log_bx logbx还是要比 ln ⁡ x \ln x lnx麻烦很多的,二分法会更省事,其次求解log在java里面采用位运算的方法,具体如何实现的可以考虑看下源码,这里不做过多讲解了。另外关于 ln ⁡ x \ln x lnx的求法可以参考wiki,利用类似于泰勒级数的方法求得:

ln ⁡ ( x + 1 ) = x − x 2 2 + x 3 3 − . . . \ln(x+1)= x- \frac{x^2}{2}+\frac{x^3}{3}-... ln(x+1)=x2x2+3x3...

或者 ln ⁡ ( 1 + 1 n ) = 2 ( − 1 2 n + 1 + 1 3 ( 2 n + 1 ) 3 + 1 5 ( 2 n + 1 ) 5 . . . ) \ln(1+\frac{1}{n})= 2(-\frac{1}{2n+1}+\frac{1}{3(2n+1)^3}+\frac{1}{5(2n+1)^5}...) ln(1+n1)=2(2n+11+3(2n+1)31+5(2n+1)51...)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值