2018.06.14******************************************************************
author:wills
我们在进行数据处理的时候,经常会遇到到一些比较复杂的数据.又或者是本来是简单的运算,但是因为运算次数过于庞大,从而导致花费大量的时间,这是如何选择合适的算法进行代码编写就是我们需要考虑的问题一种好的的算法是适用于任何编程语言的,只是不用语言的编码实现不一样.这里我就用求平方根的例子对 穷举 / 二分 / 牛顿-拉克萨方法简单介绍
我们都使用过math模块的sqrt()函数,这个函数的作用就是求一个非负数的平方根,但是我们思考过它是怎么实现的吗?或者说它是使用什么算法实现的
1. 穷举法
假设现在需要我们自己来写一个函数,实现求非负数的平方根,该怎么实现呢? 假设 x = 10 我们要求 x 的根或者求到一个数,它的平方 非常非常的接近10,这个接近的程度可以假设为误差不超过1e-8
def get_root(x=10):
cal_times = 0
ans = x / 2
step = 0.00000001
value = step ** 2
while True:
cal_times += 1
if abs(ans ** 2 - x) > value and ans ** 2 > x:
ans -= step
else:
print(ans, ' 是 ', x, '的根')
print('一共计算 %d 次' % cal_times)
break
get_root(10)
#输出结果为
# 3.1622776511687043 是 10 的根
#一共计算 183772237 次
上面这种算法的思想就是穷举法,先假设x的根为 x/2, 它肯定不是结果,然后进行无限循环,每次循环减少step,(step也可以认为就是我们所求的值的精确程度)直到找到一个值不符合
abs(ans ** 2 - x) > value and ans ** 2 > x
此时获得的ans
就是最接近x的根,或者就是x的根.这种算法一一列举了收敛区间以内的所有值,我们可以看到到精确到1e-8
时就需要循环运算183772237
次,这个效率是十分低下.
2. 二分法
上面的算法效率十分的低下,在1e-6
的精确度下需要把 x / 2 - step 循环超过一亿次.穷举法需要把每一个值进行查验,我们是否可以想个办法,每次查验的值过后下一次查验的值都里目标值更加的近呢?
例如 我们随机生成一个 1-100之间的数,猜大小最多7次就可以猜到正确答案. 假设 答案 = 23.
第一次: 50 大了
第二次: 25 大了
第三次: 12 小了
第四次: 18 小了
第五次: 21 小了
第六次: 23 正确
从上面的例子可以看出,我们每次猜测的答案都把下次猜测答案的区间 减小了 1/2,这就是二分法的核心思想,利用本次运算的结果使得答案的范围减小 一半
def get_root(x=10):
cal_times = 0
step = 0.000001
value = step ** 2
low = 0
high = x if x > 1 else 1
ans = (low + high) / 2
while True:
cal_times += 1
if abs(ans ** 2 - x) > value:
if ans ** 2 - x > value:
high = ans
elif ans ** 2 - x < value:
low = ans
else:
break
ans = (high + low) / 2
print(ans, ' 是 ', x, '的根')
print('一共计算 %d 次' % cal_times)
get_root(10)
# 结果
# 3.162277660168229 是 10 的根
# 一共计算 43 次
get_root(1)
# 0.9999999999995453 是 1 的根
#一共计算 41 次
get_root(0.6)
# 0.7745966692418733 是 0.6 的根
#一共计算 38 次
可以看到,同样的数据精度,但是二分法的效率是穷举法的几百万倍,但是二分法在有的时候并不好使用,但是穷举法几乎是处处可用
3. 牛顿 - 拉弗深法
虽然二分法的效率已经很高,但是我们介绍一种更加高效的办法,它取决于一个牛顿证明的定律
对多项式 f(x), 如果x 是f(x)根的一个近似值,那么x - f(x)/f'(x)
就是一个更好的近似值,f’(x)是f(x)的一阶导数
此例中,我们可以把ans看作是 ans ** 2 - x 根的一个近似值,那么代码如下:
def get_root(x=10):
cal_times = 0
step = 0.00000001
ans = x / 2
while abs(ans ** 2 - x) > step:
cal_times += 1
ans = ans - (ans ** 2 - x) / (2 * ans)
print(ans, ' 是 ', x, '的根')
print('一共计算 %d 次' % cal_times)
return ans
get_root(10)
# 结果
# 3.1622776604441363 是 10 的根
# 一共计算 4 次
get_root(1)
# 1.000000000000001 是 1 的根
# 一共计算 5 次
# get_root(0.6)
#0.7745966692482854 是 0.6 的根
#一共计算 5 次
可以看出最后一种算法速度最快,只要几次运算即可得出结果而且代码简洁,只是不好理解.