數據結構和算法-1-算法的引入和算法時間複雜度

最近有時間來系統學習下常見數據結構和算法的知識,所以,還是通過紀錄筆記放在博客的方式來督促自己學習。算法對於很多程序員都接觸不到的,何況是一個測試人員。但是面試過程中,多多少少都有算法題的面試。所以,學習算法,短期來看是爲了跳槽準備,長期來看,是鍛鍊一個人解決問題的思路的提升的一個途徑。

 

1.算法的引入

來看一個問題:如果 a+b+c = 1000, 且 a^2 + b^2 = c^2(勾股定理),如何求出所有a b c的組合。

問題分析:

上面告訴兩個條件,從數學角度來說,上面有3個未知數,只有兩個表達式條件,我們第一反應是轉換成二元二次方程來解答。這裏我們是計算機通過代碼來解決問題。字面意思就是 a的取值範圍是0到1000, b的取值範圍是0到1000, c的取值範圍是0到1000, 然後加上題目的兩個表達式條件,利用for嵌套循環,計算機肯定能幫我們找出a b c的取值。

代碼實現:

根據上面的分析,python代碼實現如下:

import time

start_time = time.time()
for a in range(0, 1001):
    for b in range(0, 1001):
        for c in range(0, 1001):
            if a + b + c == 1000 and a**2 + b**2 == c**2:
                print("a, b, c: %d, %d, %d" % (a, b, c))
end_time = time.time()
print(end_time - start_time)

其實上面代碼思路也是用到了一個方法,叫枚舉法,就是一個一個列出來去嘗試,不行,換下一個值繼續去匹配。

運行之後得到結果:

a, b, c: 0, 500, 500
a, b, c: 200, 375, 425
a, b, c: 375, 200, 425
a, b, c: 500, 0, 500
176.38802433013916

也就是差不多花費三分鐘(176秒多),找出了符合條件的a b c的四種取值組合。如果沒有計算機,人也是可以根據這個思路,一步一步去算,只不過時間更是不知道有多慢。這個時間開銷,我們很不滿意,對用戶來說,還是太慢了。有沒有什麼辦法提升以下計算效率。

優化上面代碼:

根據數學知識,我們用代碼實現二元二次方程的思路,c = 1000 - a - b; 來減少第三層嵌套for循環。

import time

start_time = time.time()
for a in range(0, 1001):
     for b in range(0, 1001):
        c = 1000-a-b
        if a**2 + b**2 == c**2:
            print("a, b, c: %d, %d, %d" % (a, b, c))
end_time = time.time()
print(end_time - start_time)

運行結果輸出:

a, b, c: 0, 500, 500
a, b, c: 200, 375, 425
a, b, c: 375, 200, 425
a, b, c: 500, 0, 500
1.5549931526184082

這麼一看,發現時間縮短了不到2秒,這個計算效率大大提升。同樣解決一個問題,由於我們第二種方法減少了一次for循環嵌套,導致計算效率提高了很多倍,這個就是算法的重要性。

 

2.什麼是算法

      算法是計算機處理信息的本質,因爲計算機程序本質上是一個算法來告訴計算機確切的步驟來執行一個指定的任務。一般地,當算法在處理信息時,會從輸入設備或數據的存儲地址讀取數據,把結果寫入輸出設備或者某個存儲地址供以後再調用。算法是獨立存在的一種解決問題的方法和思想。對於算法而言,實現的編程語言並不重要,重要的是思想。

算法的五大特性
1:輸入:算法具有0個或多個輸入
2:輸出:算法至少有一個或多個輸出
3:有窮性:算法在有限的步驟之後會自動結束而不會無限循環,並且每一個步驟可以在可接受的時間內完成
4:確定性:算法內的每一步都有確定的含義,不會出現二義性。
5:可行性:算法的每一步都是可行的,也就是說每一步都能執行有限的次數完成。

 

3.時間複雜度和大O表示法

上面我們通過兩個方法來求出a b c的取值組合,第二個方法比第一個方法,從時間效果來看,快很多,所以我們很容易得出結論,第二個算法比第一個算法效率要高。那麼算法是通過時間來衡量,確實最直觀地,我們從時間上來看到算法和算法之間的效率不同。但是,單靠時間是不可靠的,例如,同一個算法,在一個I7的CPU上運行和拿到一個1995年之前的個人PC電腦上運行,這種時間來比較就有點不合適了。所以,我們一般從算法的執行計算數量這個維度去考察算法的效率。執行數量可以這麼理解,上面3個for循環嵌套的代碼,每一行代碼都有確定的執行步驟數量,所有代碼行的執行步驟數量相加,就得到了這個算法的執行步驟數量。因爲每臺機器要執行這麼多步驟數量大體相同,所以這個執行步驟數量就拿來衡量算法的效率。

 

我們假定計算機執行算法每一個基本操作的時間是固定的一個時間單位,那麼有多少個基本操作就代表會花費多少時間單位。對於不同機器而言,確切的單位時間是不同的,但是對於算法進行多少個基本操作,在規模數量級上說卻是相同的。由此可以忽略機器環境影響而客觀的反應算法的時間效率。

對於算法的時間效率,我們可以用“大O記法”來表示。
“大O記法”:對於單調的整數函數f,如果存在一個整數函數g和實常數c>0,使得對於充分大得n,總有
f(n)<=c*g(n),就說函數g是f得一個漸進函數(忽略常數),記作爲f(n)=O(g(n)),也就是說在趨向無窮得
極限意義下,函數f的增長速度收到函數g的約束,亦函數f與函數g的特徵相似。


時間複雜度:假設存在函數g,使得算法A處理規模爲n的問題實例所用時間爲T(n)=O(g(n)),則稱O(g(n))爲算法A
的漸進時間複雜度,簡稱時間複雜度,記爲T(n)

 

4.如何理解“大O記法”

我們通過“大O記法”的定義,我們來計算下上面 a b c這題的第一種代碼實現方式的時間複雜度的計算過程。

我們根據上面這個圖代碼對應行來分析(第4到8行代碼),先分析每行代碼執行步驟數目。

分析過程:

第4行:a的取值範圍是0到1000,所以這個for循環要執行1000次

第5行:b的取值範圍是0到1000,所以這個for循環要執行1000次

第6行:c的取值範圍是0到1000,所以這個for循環要執行1000次

第7行:如果不細分步驟,第7和第八兩行當作2個步驟,如果細分,a + b + c是一個步驟, 判斷a + b + c ==1000是一個步驟,a**2是一個步驟,所以細分,第七行存在需要執行 8個步驟數目。

這樣,我們把每一行代碼需要執行步驟次數計算出來是

T = 1000 * 1000 * 1000 * 8
簡寫成 T = 8*1000^3

如果,這裏把1000改成n, 把這個問題規模擴大,這個算法的時間複雜度可以寫成

T(n)= 8*n^3

我們在計算時間複雜度的時候,只關注大頭部分,會去掉旁支末節部分,一般我們可以這樣認爲 n^3和1000*n^3是等價,所以我們上面文章開頭寫的第一種枚舉法的時間複雜度是 O(n^3)。

根據這個時間複雜度計算原則,我們計算第二種算法的時間複雜度爲O(n^2),這個比第一個效率要高,當然如果時間複雜度爲n^1,那麼這個算法效率就更高。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章