數據結構與算法-Python實現(一)大O表示法

全球變暖可視化精選
上圖爲:Tableau可視化精選描述全球問題經典作品

一、前言

我們正處於一個數據大爆炸的時代,2019年全球每天收發2936億封電子郵件、一輛聯網汽車每天產生4TB的數據、全世界每天有50億次在線搜索,其實當中都是數據的交互;每天打開科技專欄滿滿都是5G、大數據、人工智能、雲計算,聽過這麼一句話:生活時代交疊地方的人,會感受到兩個時代帶來的衝擊 和我們時代交疊的時代是什麼?AI 現如今呢AI到底發展到什麼程度了呢?我們社會上也出現了一種現象叫:人工智能泛化,人人都說人工智能,人人都說大數據,那到底什麼是大數據?什麼是人工智能?幾個人能講的清楚,舉一個李開復博士曾經講過的一個例子:一家做內衣的企業自稱爲人工智能企業。人工智能目前主要依賴於數據模型算法,基於歷史數據對未來進行分類或者預測,理解數據結構與算法有助於我們去理解和實現機器學習對一些算法,並且在日常編寫時我們可以用它來優化代碼,總之數據結構與算法就像是我們的內功,所以打算寫相關係列的文章用來記錄和分享,一起回顧相關的知識點

二、算法分析

如何衡量算法的好壞?我們一般是從下面兩種角度去考慮:

  • 算法消耗的計算機資源(空間複雜度):詳細的來講就是算法在執行過程中佔用的存儲空間或內存,但存儲空間會受到問題本身的數據規模的變化影響,如果要區分哪些存儲空間或內存是問題本身描述所需,哪些是算法佔用,相對來說比較困難。
  • 算法的執行時間(時間複雜度):我們可以對程序進行實際運行測試,獲得真實對運行時間

我們來用執行時間來衡量算法的性能看一個實例:

import time


def computing_time(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        over_time = time.time()
        print("Sum is %s required: %5f seconds" % (args[0], over_time - start_time))
    return inner


@computing_time
def sum_func(n):
    temp_num = 0
    for i in range(1, n+1):
        temp_num += i
    return temp_num


sum_func(100000)
sum_func(1000000)
sum_func(10000000)
sum_func(100000000)

運行結果如下:

Sum is 100000 required: 0.004882 seconds
Sum is 1000000 required: 0.054030 seconds
Sum is 10000000 required: 0.537019 seconds
Sum is 100000000 required: 5.220396 seconds

上面的代碼是一個累加求和的函數,然後使用裝飾器實現了一個程序計時功能,我們從10萬開始每次運行都是上一次的10倍,我們從結果可以看到運行時間每次也是上一次的10倍。

上面我們使用的是一個迭代算法來實現一個數的累加,現在我們使用一個無迭代的方法來實現相同的函數:

import time


def computing_time(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        over_time = time.time()
        print("Sum is %s required: %5f seconds" % (args[0], over_time - start_time))
    return inner


@computing_time
def sum_func(n):
    return (n * (n + 1)) / 2


sum_func(100000)
sum_func(1000000)
sum_func(10000000)
sum_func(100000000)

運行結果如下:

Sum is 100000 required: 0.000001 seconds
Sum is 1000000 required: 0.000001 seconds
Sum is 10000000 required: 0.000000 seconds
Sum is 100000000 required: 0.000001 seconds

我們可以看到使用無迭代的方法後,隨着參數增大我們代碼的運行時間幾乎不變;那麼利用運行時間來衡量一個算法真的合適嗎?答案是:不合適,因爲運行時間會受到程序運行環境等多方面的影響,我們需要一種衡量指標它不會受到機器環境、運行時段、編程語言的影響,運行時間無法做到這一點。

因此,另一種更爲通用的方法就出來了:「 大O符號表示法 」,即 T(n) = O(f(n))

三、大O表示法

衡量指標不能受到機器環境等影響那麼就需要一個獨立於具體程序或者機器的指標,那麼就需要一個通用的基本操作來作爲運行步驟的計量單位,每一種編程語言都有賦值語句,和具體都實現沒有關係,所以賦值語句是一個合適選擇。那麼如果按照賦值語句來衡量那麼一個算法在運行當中賦值語句多則運行時間就相對較長,賦值語句少則運行時間短,是這樣嗎?
在這裏插入圖片描述
我們可以看到當 n 被傳入函數的時候,temp_num 是第一條賦值語句,然後進入循環後賦值語句的數據恰好等於 n 所以賦值語句的數量等於 T(n) = n + 1 那麼 n 的大小就決定了賦值語句的次數,就決定了程序運行的時間,所以 n 被稱爲:問題規模 我們分析算法的目標就是要找出 問題規模 是如何影響一個算法的 運行時間 它們之前存在着怎樣的函數關係。我們現在來細品 T(n) = n + 1 這個函數,我們看到 n 是T(n)函數中起決定性的因素,從動態的眼光看,無論函數多複雜,當問題達到某個數量級的時候,T(n)中的一部分會蓋過其它部分的貢獻,我們把函數中這一部分稱爲:數量級函數,它描述了T(n)中隨着n增加而增加速度最快的主導部分,這種數量級函數,稱爲:大O表示法,記作 O(f(n)) 其中 f(n) 表示 T(n) 中的主導部分,什麼是主導部分相信大家心裏都有數了吧!我們一起來看下面的栗子:

  • T(n) = 1 + n (剛纔那個累加函數)
    當 n 增大時,常數1在對結果的貢獻(影響)可以忽略,所以可以去掉1,保留n作爲主要部分,那麼運行時間數量級就是 O(n) n 增大運行時間也線性增大
    n+1

  • T(n) = 5n^2 + 20n + 1000
    當 n 很小時,常數1000 對結果對貢獻最大,但當 n 增大時 n^2 就會越來越重要,其它兩項對結果影響就越來越小,同學係數 5 對於 n^2 的增長速度也不大,也可以忽略,所以可以在數量級中去掉 20n+1000 以及係數部分,大O表示法結果爲:O(n^2)

  • 從代碼來分析算法複雜度

a = 10
b = 12
c = 15
for i in range(n):
    for j in range(n):
        x = i * i
        y = j * j
        z = i * j
for k in range(n):
    r = a * k + 45
    v = b * b
d = 50

那麼 T(n) = 3 + 3n^2 +2n +1 (賦值語句) 合併同類項後:T(n) = 3n^2 + 2n + 4 按照上述的方法分析,只保留高階項n^2,去掉係數,表示爲: O(n^2)

ok!大O表示法就介紹完了,Bye~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章