算法题 - 求一个正数的开方根 - Python

求一个正数的开方根

问题描述:

给定一个正数 a,求这个数的开方根,要求保留精度小数点后6位。

问题分析:

这个题目是今天火山小视频第一个面试题,其实之前就遇到过,LeetCode:69. Sqrt(x)和这个不同的是,要求是保留小数点后六位,但是无外乎还是使用二分法或者牛顿迭代法来做。这次特定介绍一种前几天看到的一个方法,梯度下降法(在朋友的博客上看到的,但是他没有详细的整理和给出可运行的代码:链接)。那么现在先介绍一下牛顿迭代法,然后是梯度下降法,最后是二分法。

(1)方法一:牛顿迭代法(最常用的方法):

把问题转换成,求解函数 f ( x ) = x 2 − a = 0 f(x) = x^{2} - a=0 f(x)=x2a=0的一个根。现在看看牛顿迭代公式,不用详细介绍了,很简单:
其中: f ′ ( x ) = 2 x f'(x) = 2x f(x)=2x
算法题 - 求一个正数的开方根 - Python_第1张图片
(图片来自百度百科)

# @Time   :2019/02/12
# @Author :LiuYinxing
# Python3
# 牛顿迭代法


class Solution:
    def __init__(self, th=1e-4):
        self.th = th  # 设置阈值threshold  th=1e-4

    def sqrt(self, a):
        if a <= 0:  # 非正数均返回 0 ,不做进一步细分了哈
            return 0

        x = 1.0  # 设一个初始值(初始化如果为负值,找到的是负根)
        while abs(x*x-a) >= self.th:  # 判断是否收敛
            x = (x + a / x) / 2  # (x*x - a)/(2x)
        return x


if __name__ == '__main__':
    solu = Solution()
    print(solu.sqrt(0.09))

(2)梯度下降法
x = a x = \sqrt{a} x=a ,那么问题转换一下, f ( x ) = x 2 − a f(x) = x^{2} - a f(x)=x2a,即求 f ( x ) = 0 f(x)=0 f(x)=0 的一个解。现在问题是,如何把这个问题转换成梯度下降问题,一般的认识是梯度下降法是求解极值问题,我们把这个问题转换成极值问题即可,不难看出, f ( x ) = x 2 − a f(x) = x^{2} - a f(x)=x2a 是函数 F ( x ) = 1 3 x 3 − a x F(x) = \frac{1}{3}x^{3} - ax F(x)=31x3ax x x x求导的导函数。

现在对函数 F ( x ) = 1 3 x 3 − a x F(x) = \frac{1}{3}x^{3} - ax F(x)=31x3ax 进行梯度下降求极值,求得的那个解,很显然就是 f ( x ) = x 2 − a = 0 f(x) = x^{2} - a=0 f(x)=x2a=0的一个根了。

Python3实现:

# @Time   :2019/02/12
# @Author :LiuYinxing
# Python3
# 梯度下降法


class Solution:
    def __init__(self, lr=0.01, th=1e-4):
        self.lr = lr  # 设置学习率 lr=0.01
        self.th = th  # 设置阈值threshold  th=1e-4

    def sqrt(self, a):
        if a <= 0:  # 非正数均返回 0 ,不做进一步细分了哈
            return 0

        x = 5  # 设一个初始值
        F_cur = x * x * x / 3 - a * x  # 计算初始函数值
        error = 1
        while abs(error) >= self.th:  # 判断是否收敛
            x -= self.lr * (x * x - a)  # 迭代求x,其实相当于更新梯度
            error = F_cur - (x * x * x / 3 - a * x)
            F_cur = x * x * x / 3 - a * x
        return x


if __name__ == '__main__':
    solu = Solution(lr=0.01)
    print(solu.sqrt(88))

上面代码存在什么样的问题:

问题1:很显然它有两个根,求出的是不是正数?

:函数 F ( x ) = 1 3 x 3 − a x F(x) = \frac{1}{3}x^{3} - ax F(x)=31x3ax 如何画出它的坐标图,那么它一定是一个先递增–>递减–>递增的图像,很显然就一个极小值点(还一个极大值点)。所以理论上只要初始化 x x x 的值 在 ( − a , + ∞ ] (- \sqrt{a}, +\infty] (a ,+] 范围内,就可以迭代到极小值点。(感觉是这样哈,看下图辅助理解)
算法题 - 求一个正数的开方根 - Python_第2张图片
问题2:尝试了一下,因为要求三次方,所以 a a a 比较大的情况下,就要考虑调整参数了,而且是求解要消耗一定时间

(3)方法三:二分法(简单有效哈)

# @Time   :2019/02/12
# @Author :LiuYinxing
# Python3  二分法


class Solution:
    def sqrt(self, x, th):

        if x <= 0:return 0  # 非正数均返回 0 ,不做进一步细分了哈

        if x >= 1:  # 初始化要分两个区间进行,[0, 1], [1, 正无穷]
            low, high = 1, x
        else:
            low, high = x, 1

        while high - low > th:
            mid = (low + high) / 2
            mid2 = mid ** 2
            if mid2 == x:
                return mid
            elif mid2 < x:
                low = mid + th
            elif mid2 > x:
                high = mid - th
        return high


if __name__ == '__main__':
    solu = Solution()
    print(solu.sqrt(0.09, th=1e-6))

声明: 总结学习,有问题或不当之处,可以批评指正哦,谢谢。

你可能感兴趣的:(算法,Python)