複雜度分析
複雜度分析在我看來是資料結構與演算法學習入門知識,尤為重要。
為什麼複雜度分析重要?
資料結構與演算法的出現本就是為了花更少的時間和空間(儲存)來解決問題。複雜度分析就是為解決如何“花更少的時間和空間(儲存)”的問題。
現在各種編譯工具,程式碼跑完就能顯示用了多少時間,佔了多少記憶體。但是這些資料都是在完成程式碼編寫之後才能得到的,這是事後統計方法。
事後統計法得到的結果會因計算機效能和測試資料規模不同而有較大差異。這並不能體現出程式碼本身的效率、優劣程度,而且很可能寫出的程式碼本身就不太行。。。
而複雜度分析不依賴軟硬體效能、資料規模等就能直接估算演算法的效率、優劣,這就是其重要性。
時間複雜度
時間複雜度即演算法的執行時間。
用執行時間去描述演算法的效率時,演算法的執行總步數越多演算法越慢,總步數越少則越快。
假設每一行程式碼執行時間都為X,則演算法的總執行時間等於執行的總程式碼行數。
1 def sum(n): 2 sum = 0 3 4 for i in range(n): 5 sum += i 6 return sum
在上面程式碼中,假設執行一行需要 1t,則第二行執行時間為 1t,第四行、第五行執行了n遍,每行執行時間為 nt,則執行總時間就是(1+2n)t
用函式來表示就是 T(n) = (1+2n) t,可以看出T(n)和總步數成正比關係。
用 大O表示法 就是 T(n) = O(f(n)),f(n)為執行總步數。
n可以是1到+∞,當其非常大的時候,一些相對於n來說小的部分就可以忽略了。
拿前面的 1+2n 來說,當n = 100000時,1+2n = 2000001,n持續變大,常數1和係數2對結果影響就很微小了,直接忽略,最終變成了 T(n) = O(n)。
下面例子作為練習:
1 def sum(n): 2 sum = 0 3 4 for i in range(n): 5 for j in range(n): 6 sum += i + j 7 return sum
其中有個巢狀for迴圈,第四行執行1次,第五行、第六行執行n次
第二行需要執行1次,第四行執行n次,第五行、第六行執行n2次,總步數 f(n) = 1 +n +2n2
當n非常大的時候,對運算結果有影響的是n2,常數1、n、係數2都可以忽略,則 f(n) = n2,程式碼執行時間 T(n) = O(n2)。
常見時間複雜度
最好情況、最壞情況和平均情況時間複雜度
一段程式碼中,資料的具體情況也會影響執行時間
def test(list, num): index = -1 for i in range(len(list)): if list[i] == num: index = i break return index
這段程式碼是求 變數num 在上列表 list 中的索引
假設輸入的 list = [1,2,3,4,5]
當 num = 1 時,自第一次執行時就在list中找到了,後面不再遍歷,此時時間複雜度為 O(1)。
當 num = 5 時,全部遍歷完才找到,此時時間複雜度為 O(n)。
當 num = 6 時,全部遍歷完也沒找到,此時時間複雜度也為 O(n)。
這就是資料情況不同,時間複雜度不同。
這樣就出現 最好情況、最壞情況時間複雜度。
最好情況時間複雜度就是最理想情況下程式碼複雜度,對應上面例子最好時間複雜度就是O(1);
最壞情況時間複雜度就是最不理想情況下程式碼複雜度,對應上面例子最好時間複雜度就是O(n);
平均時間複雜度根據加權平均值求得。拿上面例子來說就是 O((3n+1)/4) = O(n)
像最好情況時間複雜度,平均時間複雜度並沒有太大參考價值,一般都是拿最壞時間複雜度當時間複雜度,因為不會有比最壞的情況再壞的了,可以說最壞時間複雜度就是一個底線。
空間複雜度
空間複雜度反應的是程式碼執行過程中臨時變數佔用的記憶體空間。
程式碼執行所佔空間分為三種:1、程式碼本身所佔空間;2、輸入資料所佔空間;3、臨時變數所佔空間
1和2與程式碼效能無關,所以空間複雜度只考慮執行過程臨時佔用空間
空間複雜度記作S(n),表示形式同樣為O(f(n))
def sum(list): sum = 0 for i in range(len(list)): sum += list[i] return sum
該程式碼求輸入列表list中所有元素的和。
其中,輸入的list空間不計,sum儲存和,i儲存元素索引,都是常數,其所佔空間與n沒有關係,所以該程式碼空間複雜度S(n) = O(1)
常見空間複雜度
O(n):
def createList(n): list = [] for i in range(n): list.append(i) return list
其中臨時變數有 list和i
list為空列表,所佔用空間根據for迴圈次數增加,最大為n,所以list的空間複雜度為O(n),i為常數階,與n無關,則整個程式碼空間複雜度為O(n)
O(n2):
def createList(n): list1 = [] for i in range(n): list2 = [] for j in range(n): list2.append(j) list1.append(list2) return list1
這段程式碼比上面那個多了一層巢狀for迴圈,list1的空間佔用為n,list2的空間佔用為n2,則整個程式碼空間複雜度為O(n2)