1. 程式人生 > >演算法學習 一 窮舉

演算法學習 一 窮舉

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 次

可以看出最後一種演算法速度最快,只要幾次運算即可得出結果而且程式碼簡潔,只是不好理解.