先來看一道題:python
若是 a+b+c=1000,且 a^2+b^2=c^2(a、b、c爲天然數),如何求出全部a、b、c可能的組合?算法
1 import time 2 3 start_time = time.time() 4 5 # 三重循環 6 for a in range(1001): 7 for b in range(1001): 8 for c in range(1001): 9 if a + b + c == 1000 and a**2 + b**2 == c**2: 10 print("a:{}, b:{}, c:{}".format(a, b, c)) 11 12 end_time = time.time() 13 print("used time: %i" % end_time - start_time) 14 print("complete!")
運行結果:數據結構
a:0, b:500, c:500 a:200, b:375, c:425 a:375, b:200, c:425 a:500, b:0, c:500 used time: 620 seconds complete!
算法是計算機處理信息的本質,由於計算機程序本質上是一個算法來告訴計算機按照確切的步驟來執行一個指定的任務。通常地,當算法在處理信息時,會從輸入設備或數據的存儲地址讀取數據,把結果寫入輸出設備或某個存儲地址供之後再調用。app
算法是獨立存在的一種解決問題的方法和思想。對於算法而言,實現的語言並不重要,重要的是思想。函數
算法能夠有不一樣的語言描述實現版本(如C描述、C++描述、Python描述等),咱們如今是在用Python語言進行描述實現。性能
1 import time 2 3 start_time = time.time() 4 5 # 兩重循環 6 for a in range(1001): 7 for b in range(1001): 8 c = 1000 - a - b 9 if a**2 + b**2 == c**2: 10 print("a:{}, b:{}, c:{}".format(a, b, c)) 11 12 end_time = time.time() 13 print("used time: %i seconds" % (end_time - start_time)) 14 print("complete!")
運行結果:測試
a:0, b:500, c:500 a:200, b:375, c:425 a:375, b:200, c:425 a:500, b:0, c:500 used time: 4 seconds complete!
對於同一問題,咱們給出了兩種解決算法,在兩種算法的實現中,咱們對程序執行的時間進行了測算,發現兩段程序執行的時間相差懸殊(620秒相比於4秒),由此咱們能夠得出結論:實現算法程序的執行時間能夠反應出算法的效率,即算法的優劣。spa
假設咱們將第二次嘗試的算法程序運行在一臺配置古老性能低下的計算機中,狀況會如何?極可能運行的時間並不會比在咱們的電腦中運行算法一的執行時間快多少。所以,單純依靠運行的時間來比較算法的優劣並不必定是客觀準確的!操作系統
程序的運行離不開計算機環境(包括硬件和操做系統),這些客觀緣由會影響程序運行的速度並反應在程序的執行時間上。那麼如何才能客觀的評判一個算法的優劣呢?設計
咱們假定計算機執行算法時每個基本操做(即計算步驟)的時間是固定的一個時間單位,那麼有多少個基本操做就表明會花費多少時間單位。固然,對於不一樣的機器環境而言,確切的單位時間是不一樣的,可是對於算法進行多少個基本操做(即花費多少時間單位)在規模數量級上倒是相同的,由此能夠忽略機器環境的影響而客觀地反應算法的時間效率。
即時間複雜度就是用算法所需的步驟數量來衡量其效率。那麼,如何來斷定所需步驟數量的標準呢?此時可使用「大O記法」。
對於算法的時間效率,咱們能夠用「大O計法」來表示,如下從數學上理解:
通俗理解:當解決問題的計算步驟跟n相關時,把旁支末節(如其相關係數)所有忽略掉,只留下最關鍵的特徵(n的部分),就是大O表示法。
以上述引入的示例代碼爲例:
# 例1:a+b+c=1000,且 a^2+b^2=c^2(a,b,c 爲天然數) for a in range(1001): # 基本計算步驟爲1000次 for b in range(1001): # 1000次 for c in range(1001): # 1000次 if a + b + c == 1000 and a**2 + b**2 == c**2: # 1次 print("a:{}, b:{}, c:{}".format(a, b, c)) # 1次 # 例2:若是改成 a+b+c=2000,且 a^2+b^2=c^2(a,b,c 爲天然數)呢?
簡單劃分示例中的計算步驟數量:
當使用大O計法時,去掉相關係數2,只會留下n^3,記爲 g(n) = n^3 。此時,可說T(n) = g(n)。
所以,即便相關係數有所變化,如T(n) = n * n * n * 2 = n^3 * 10,咱們也認爲二者(n^3 * 2 與 n^3 * 10)效率「差很少」。
分析算法時,存在幾種可能的考慮:
對於最優時間複雜度,其價值不大,由於它沒有提供什麼有用信息,其反映的只是最樂觀最理想的狀況,沒有參考價值。
對於最壞時間複雜度,提供了一種保證,代表算法在此種程度的基本操做中必定能完成工做。
對於平均時間複雜度,是對算法的一個全面評價,所以它完整全面的反映了這個算法的性質。但另外一方面,這種衡量並無保證,不是每一個計算都能在這個基本操做內完成。並且,對於平均狀況的計算,也會由於應用算法的實例分佈可能並不均勻而難以計算。
所以,咱們主要關注算法的最壞狀況,亦即最壞時間複雜度。
示例:
1 for a in range(n): # 循環 2 for b in range(n): # 循環 3 c = 1000 - a - b # 基本操做 4 if a**2 + b**2 == c**2: # 分支:要麼進入分支中的print,要麼退出 5 print("a:{}, b:{}, c:{}".format(a, b, c)) # 基本操做
其時間複雜度:T(n)
= n * n * (1 + max(1, 0))
= n^2 * 2
= O(n^2)
1 for a in range(0, 1001): 2 for b in range(0, 1001): 3 for c in range(0, 1001): 4 if a**2 + b**2 == c**2 and a+b+c == 1000: 5 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): 2 for b in range(0, 1001-a): 3 c = 1000 - a - b 4 if a**2 + b**2 == c**2: 5 print("a, b, c: %d, %d, %d" % (a, b, c))
時間複雜度:T(n) = O(n*n*(1+1)) = O(n*n) = O(n2)
因而可知,咱們嘗試的第二種算法要比第一種算法的時間複雜度好得多。
注意,常常將log2n(以2爲底的對數)簡寫成logn
所消耗的時間從小到大:
Python3中的timeit模塊能夠用來測試小段代碼的運行時間。其中主要經過兩個函數來實現:timeit和repeat,代碼以下:
def timeit(stmt="pass", setup="pass", timer=default_timer, number=default_number, globals=None): """Convenience function to create Timer object and call timeit method.""" return Timer(stmt, setup, timer, globals).timeit(number) def repeat(stmt="pass", setup="pass", timer=default_timer, repeat=default_repeat, number=default_number, globals=None): """Convenience function to create Timer object and call repeat method.""" return Timer(stmt, setup, timer, globals).repeat(repeat, number)
在上面的代碼中可見,不管是timeit仍是repeat都是先生成Timer對象,而後調用了Timer對象的timeit或repeat函數。
在使用timeit模塊時,能夠直接使用timeit.timeit()、tiemit.repeat(),還能夠先用timeit.Timer()來生成一個Timer對象,而後再用TImer對象用timeit()和repeat()函數,後者再靈活一些。
上述兩個函數的入參:
1 import timeit 2 3 # ----生成列表的效率---- 4 5 def t1(): 6 l = [] 7 for i in range(1000): 8 l = l + [i] 9 10 def t2(): 11 l = [] 12 for i in range(1000): 13 l.append(i) 14 15 def t3(): 16 l = [i for i in range(1000)] 17 18 def t4(): 19 l = list(range(1000)) 20 21 22 t1 = timeit.timeit("t1()", setup="from __main__ import t1", number=1000) 23 print("+ used time:{} seconds".format(t1)) 24 print() 25 t2 = timeit.timeit("t2()", setup="from __main__ import t2", number=1000) 26 print("append used time:{} seconds".format(t2)) 27 print() 28 t3 = timeit.timeit("t3()", setup="from __main__ import t3", number=1000) 29 print("[i for i in range(n)] used time:{} seconds".format(t3)) 30 print() 31 t4 = timeit.timeit("t4()", setup="from __main__ import t4", number=1000) 32 print("list(range(n)) used time:{} seconds".format(t4)) 33 print() 34 35 # ----pop元素的效率---- 36 37 x = list(range(1000000)) 38 pop_from_zero = timeit.timeit("x.pop(0)", setup="from __main__ import x", number=1000) 39 print("pop_from_zero used time:{} seconds".format(pop_from_zero)) 40 print() 41 x = list(range(1000000)) 42 pop_from_last = timeit.timeit("x.pop()", setup="from __main__ import x", number=1000) 43 print("pop_from_last used time:{} seconds".format(pop_from_last))
運行結果:
+ used time:3.7056619 seconds
append used time:0.46458129999999986 seconds
[i for i in range(n)] used time:0.18458229999999975 seconds
list(range(n)) used time:0.0845849000000003 seconds
pop_from_zero used time:0.5516430999999997 seconds
pop_from_last used time:0.0002724000000000615 seconds
需求:咱們如何用Python中的類型來保存一個班的學生信息? 若是想要快速的經過學生姓名獲取其信息呢?
實際上當咱們在思考這個問題的時候,咱們已經用到了數據結構。列表和字典均可以存儲一個班的學生信息,可是想要在列表中獲取一名同窗的信息時,就要遍歷這個列表,其(最壞)時間複雜度爲O(n)。而使用字典存儲時,可將學生姓名做爲字典的鍵,學生信息做爲值,進而查詢時不須要遍歷即可快速獲取到學生信息,其時間複雜度爲O(1)。
咱們爲了解決問題,須要將數據保存下來,而後根據數據的存儲方式來設計算法實現進行處理,那麼數據的存儲方式不一樣就會致使須要不一樣的算法進行處理。咱們但願算法解決問題的效率越快越好,因而就須要考慮數據究竟如何保存的問題,這就是數據結構。
在上面的問題中咱們能夠選擇Python中的列表或字典來存儲學生信息。列表和字典就是Python內建幫咱們封裝好的兩種數據結構。
數據是一個抽象的概念,將其進行分類後獲得程序設計語言中的基本類型。如:int,float,char等。數據元素之間不是獨立的,存在特定的關係,這些關係即是結構。數據結構指數據對象中數據元素之間的關係。
Python給咱們提供了不少現成的數據結構類型,這些系統本身定義好的,不須要咱們本身去定義的數據結構就叫作Python的內置數據結構,好比列表、元組、字典。而有些數據組織方式,Python系統裏面沒有直接定義,須要咱們本身去定義實現這些數據的組織方式,這些數據組織方式稱之爲Python的擴展數據結構,好比棧,隊列等。
數據結構只是靜態的描述了數據元素之間的關係。
高效的程序須要在數據結構的基礎上設計和選擇算法。
程序 = 數據結構 + 算法
總結:算法是爲了解決實際問題而設計的,數據結構是算法須要處理的問題載體。
抽象數據類型(ADT)的含義是指一個數學模型以及定義在此數學模型上的一組操做。即把數據類型和數據類型上的運算捆在一塊兒,進行封裝。
引入抽象數據類型的目的是把數據類型的表示和數據類型的運算實現,與這些數據類型和運算在程序中的引用隔開,使它們相互獨立。
最經常使用的數據運算有五種: