演算法基礎筆記_時間複雜度
大O的理解:
- n表示資料規模
- O(f(n)) 表示執行演算法所需要執行的指令數(執行時間),和f(n)成正比
- O(f(n)) 可以理解為: t = a*f(n) + b = f(n)
- 簡單驗證程式演算法複雜度的方法:將資料規模依次成倍增長,檢視執行時間的增長規律,或作出 t-n 的座標圖觀察。
- “均攤複雜度” 和 “複雜度震盪”
- 二分查詢法 O(logn) 所需執行指令數:a*logn
- 尋找陣列中的最大/最小值 O(n) 所需執行指令數:b*n
- 歸併排序演算法 O(nlogn) 所需指令數:c*nlogn
- 選擇排序法 O (n^2) 所需指令數:d*n^2
O(nlogn+n) = O(nlogn)
O(nlogn+n^2) = O(n^2)
資料規模量不一致時:
O(AlogA+B) != O(AlogA)
O(AlogA+B^2) != O(B^2)
對鄰接表實現的圖的遍歷:
時間複雜度:O(V+E)
V:頂點個數; E:邊的個數
二分查詢法的時間複雜度是O(logn)的理解:
二分查詢的執行步驟:在n個元素中尋找 -> 在n/2個元素中尋找 -> 在n/4個元素中尋找 -> ... 在一個元素中尋找
n經過 t 次“除以2”操作後等於1 -> ->
可推:n經過 t 次 “除以k” 的操作後等於1 —> 時間複雜度:
注: , 因為 , 而 是一個常數。
O(nlogn)的例子:外層迴圈是一個t次“乘以2”操作到達n的過程,是一個O(logn), 內層迴圈是一個O(n),故雙層迴圈巢狀後為O(nlogn)
for(int i=1; i<n; i+=i){ for(int j=1; j<n; j++){ //do something } }
O()的例子:i一步一步到達。
for(int i=1; i*i<n; i++){ //do something }
遞迴演算法的時間複雜度分析:
- 遞迴函式中,只進行一次遞迴呼叫,遞迴深度為depth, 在每個遞迴函式中,時間複雜度為T,則總體的時間複雜度為O(T*depth)
- 遞迴函式中,多次進行遞迴呼叫,要考慮遞迴深度、遞迴呼叫次數、每個遞迴函式中的時間複雜度。 具體可查閱“主定理”知識。
一個時間複雜度問題:
有一個字串陣列,將陣列中每一個字串按照字母排序;之後再將整個字串陣列按照字典序排序,求整個操作的時間複雜度。
錯誤解法:O(n*nlogn+nlogn)=O(n^2logn)
正確思路:假設最長的字串長度為s, 陣列中有n個字串;(排序演算法中,nlogn表示的是比較的次數)
對每個字串排序:O(slogs),而陣列中有n個字串,則:O(n*slogs);
將整個字串陣列按照字典序排序:O(s*nlogn)
綜上:O(n*slogs) + O(s*nlogn) = O(n*s*logs+s*n*logn)
演算法複雜度在有些情況下是用例相關的 :
插入排序演算法O(n^2): 快速排序演算法O(nlogn):
最差情況: O(n^2); 最差情況: O(n^2);
最好情況: O(n); 最好情況: O(nlogn)
平均情況: O(n^2) 平均情況: O(nlogn)
對資料規模的概念:
如果想要在1s之內解決問題:
- O(n^2)的演算法可以處理大約10^4級別的資料;
- O(n)的演算法可以處理大約10^8級別的資料;
- O(nlogn)的演算法可以處理大約10^7級別的資料;
為了保險起見,可以降低一個數量級。
空間複雜度:
- 多開一個輔助的陣列:O(n)
- 多開一個輔助的二維陣列:O(n^2)
- 多開常數空間:O(1) 即開一些臨時的變數儲存一些臨時的值,不論資料規模n的大小,開出的儲存空間是一定的。
注意:遞迴的呼叫是有空間代價的,遞迴過程中未退出的狀態要壓入系統棧中,空間複雜的與遞迴的深度有關。