算法概念篇

若是 a+b+c=1000,且 a^2+b^2=c^2(a,b,c 爲天然數),如何求出全部a、b、c可能的組合?算法

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**2 + b**2 == c**2 and a+b+c == 1000:
                print("a, b, c: %d, %d, %d" % (a, b, c))

end_time = time.time()
print("elapsed: %f" % (end_time - start_time))
print("complete!")

運行結果:函數

a, b, c: 0, 500, 500
a, b, c: 200, 375, 425
a, b, c: 375, 200, 425
a, b, c: 500, 0, 500
elapsed: 214.583347
complete!

注意運行的時間:214.583347秒,好慢呀!代碼好low。(>=<)!性能

 

算法的概念

算法是計算機處理信息的本質,由於計算機程序本質上是一個算法來告訴計算機確切的步驟來執行一個指定的任務。通常地,當算法在處理信息時,會從輸入設備或數據的存儲地址讀取數據,把結果寫入輸出設備或某個存儲地址供之後再調用。spa

算法是獨立存在的一種解決問題的方法和思想。操作系統

對於算法而言,實現的語言並不重要,重要的是思想。code

算法能夠有不一樣的語言描述實現版本(如C描述、C++描述、Python描述等),咱們如今是在用Python語言進行描述實現。blog

算法的五大特性

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

 

第二次嘗試,上算法開山斧

import time

start_time = time.time()

# 注意是兩重循環
for a in range(0, 1001):
    for b in range(0, 1001-a):
        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("elapsed: %f" % (end_time - start_time))
print("complete!")

運行結果:table

a, b, c: 0, 500, 500
a, b, c: 200, 375, 425
a, b, c: 375, 200, 425
a, b, c: 500, 0, 500
elapsed: 0.182897
complete!

注意運行的時間:0.182897秒,快了百倍。class

 

算法效率衡量

執行時間反應算法效率

對於同一問題,咱們給出了兩種解決算法,在兩種算法的實現中,咱們對程序執行的時間進行了測算,發現兩段程序執行的時間相差懸殊(214.583347秒相比於0.182897秒),由此咱們能夠得出結論:實現算法程序的執行時間能夠反應出算法的效率,即算法的優劣。效率

單靠時間值絕對可信嗎?

假設咱們將第二次嘗試的算法程序運行在一臺配置古老性能低下的計算機中,狀況會如何?極可能運行的時間並不會比在咱們的電腦中運行算法一的214.583347秒快多少。

單純依靠運行的時間來比較算法的優劣並不必定是客觀準確的!

程序的運行離不開計算機環境(包括硬件和操做系統),這些客觀緣由會影響程序運行的速度並反應在程序的執行時間上。那麼如何才能客觀的評判一個算法的優劣呢?

時間複雜度與「大O記法」

咱們假定計算機執行算法每個基本操做的時間是固定的一個時間單位,那麼有多少個基本操做就表明會花費多少時間單位。算然對於不一樣的機器環境而言,確切的單位時間是不一樣的,可是對於算法進行多少個基本操做(即花費多少時間單位)在規模數量級上倒是相同的,由此能夠忽略機器環境的影響而客觀的反應算法的時間效率。

對於算法的時間效率,咱們能夠用「大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)

如何理解「大O記法」

對於算法進行特別具體的細緻分析雖然很好,但在實踐中的實際價值有限。對於算法的時間性質和空間性質,最重要的是其數量級和趨勢,這些是分析算法效率的主要部分。而計量算法基本操做數量的規模函數中那些常量因子能夠忽略不計。例如,能夠認爲3n2和100n2屬於同一個量級,若是兩個算法處理一樣規模實例的代價分別爲這兩個函數,就認爲它們的效率「差很少」,都爲n2級。

最壞時間複雜度

分析算法時,存在幾種可能的考慮:

  • 算法完成工做最少須要多少基本操做,即最優時間複雜度
  • 算法完成工做最多須要多少基本操做,即最壞時間複雜度
  • 算法完成工做平均須要多少基本操做,即平均時間複雜度

對於最優時間複雜度,其價值不大,由於它沒有提供什麼有用信息,其反映的只是最樂觀最理想的狀況,沒有參考價值。

對於最壞時間複雜度,提供了一種保證,代表算法在此種程度的基本操做中必定能完成工做。

對於平均時間複雜度,是對算法的一個全面評價,所以它完整全面的反映了這個算法的性質。但另外一方面,這種衡量並無保證,不是每一個計算都能在這個基本操做內完成。並且,對於平均狀況的計算,也會由於應用算法的實例分佈可能並不均勻而難以計算。

所以,咱們主要關注算法的最壞狀況,亦即最壞時間複雜度。

時間複雜度的幾條基本計算規則

  1. 基本操做,即只有常數項,認爲其時間複雜度爲O(1)
  2. 順序結構,時間複雜度按加法進行計算
  3. 循環結構,時間複雜度按乘法進行計算
  4. 分支結構,時間複雜度取最大值
  5. 判斷一個算法的效率時,每每只須要關注操做數量的最高次項,其它次要項和常數項能夠忽略
  6. 在沒有特殊說明時,咱們所分析的算法的時間複雜度都是指最壞時間複雜度

 

算法分析

 

  1. 第一次嘗試的算法核心部分
    for a in range(0, 1001):
        for b in range(0, 1001):
            for c in range(0, 1001):
                if a**2 + b**2 == c**2 and a+b+c == 1000:
                    print("a, b, c: %d, %d, %d" % (a, b, c))

    時間複雜度:

    T(n) = O(n*n*n) = O(n3)

 

  1. 第二次嘗試的算法核心部分  
for a in range(0, 1001):
    for b in range(0, 1001-a):
        c = 1000 - a - b
        if a**2 + b**2 == c**2:
            print("a, b, c: %d, %d, %d" % (a, b, c))

  時間複雜度:

  T(n) = O(n*n*(1+1)) = O(n*n) = O(n2)

因而可知,咱們嘗試的第二種算法要比第一種算法的時間複雜度好多的。

 

常見時間複雜度

執行次數函數舉例 非正式術語
12 O(1) 常數階
2n+3 O(n) 線性階
3n2+2n+1 O(n2) 平方階
5log2n+20 O(logn) 對數階
2n+3nlog2n+19 O(nlogn) nlogn階
6n3+2n2+3n+4 O(n3) 立方階
2n O(2n) 指數階

注意,常常將log2n(以2爲底的對數)簡寫成logn

常見時間複雜度之間的關係

 

所消耗的時間從小到大

O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

練習: 時間複雜度練習( 參考算法的效率規則判斷 )O(5)O(2n + 1)O(n²+ n + 1)O(3n³+1)

相關文章
相關標籤/搜索