資料結構與演算法之美專欄學習筆記-複雜度分析
複雜度分析
什麼是複雜度分析
資料結構和演算法解決是“如何讓計算機更快時間、更省空間的解決問題”。
因此需從執行時間和佔用空間兩個維度來評估資料結構和演算法的效能。
分別用時間複雜度和空間複雜度兩個概念來描述效能問題,二者統稱為複雜度。
複雜度描述的是演算法執行時間(或佔用空間)與資料規模的增長關係。
為什麼要進行復雜度分析
和效能測試相比,複雜度分析有不依賴執行環境、成本低、效率高、易操作、指導性強的特點。
掌握複雜度分析,將能編寫出效能更優的程式碼,有利於降低系統開發和維護成本。
如何進行復雜度分析
大O表示法
演算法的執行時間與每行程式碼的執行次數成正比,用T(n) = O(f(n))表示
其中T(n)表示演算法執行總時間,f(n)表示每行程式碼執行總次數,而n往往表示資料的規模。
特點
以時間複雜度為例,由於時間複雜度描述的是演算法執行時間與資料規模的增長變化趨勢,
所以常量階、低階以及係數實際上對這種增長趨勢不產決定性影響,所以在做時間複雜度分析時忽略這些項。
複雜度分析法則
單段程式碼看高頻:比如迴圈。
多段程式碼取最大:比如一段程式碼中有單迴圈和多重迴圈,那麼取多重迴圈的複雜度。
巢狀程式碼求乘積:比如遞迴、多重迴圈等
多個規模求加法:比如方法有兩個引數控制兩個迴圈的次數,那麼這時就取二者複雜度相加。
常用的複雜度級別
多項式階
隨著資料規模的增長,演算法的執行時間和空間佔用,按照多項式的比例增長。包括
O(1)(常數階)
O(logn)(對數階)
O(n)(線性階)
O(nlogn)(線性對數階)
O(n^2)(平方階)
O(n^3)(立方階)
非多項式階
隨著資料規模的增長,演算法的執行時間和空間佔用暴增,這類演算法效能極差。包括,
O(2^n)(指數階)
O(n!)(階乘階)
如何掌握好複雜度分析方法
複雜度分析關鍵在於多練,所謂孰能生巧。
複雜度分析的4個概念
最壞情況時間複雜度
程式碼在最理想情況下執行的時間複雜度。
最好情況時間複雜度
程式碼在最壞情況下執行的時間複雜度。
平均時間複雜度
用程式碼在所有情況下執行的次數的加權平均值表示。
均攤時間複雜度
在程式碼執行的所有複雜度情況中絕大部分是低級別的複雜度,個別情況是高級別複雜度且發生具有時序關係時,
可以將個別高級別複雜度均攤到低級別複雜度上。基本上均攤結果就等於低級別複雜度。
為什麼要引入這4個概念
同一段程式碼在不同情況下時間複雜度會出現量級差異,為了更全面,更準確的描述程式碼的時間複雜度,所以引入這4個概念。
程式碼複雜度在不同情況下出現量級差別時才需要區別這四種複雜度。大多數情況下,是不需要區別分析它們的。
如何分析平均、均攤時間複雜度?
平均時間複雜度
程式碼在不同情況下複雜度出現量級差別,則用程式碼所有可能情況下執行次數的加權平均值表示。
均攤時間複雜度
兩個條件滿足時使用:
1)程式碼在絕大多數情況下是低級別複雜度,只有極少數情況是高級別複雜度;
2)低級別和高級別複雜度出現具有時序規律。均攤結果一般都等於低級別複雜度。
思考題
分析下面add方法的程式碼的時間複雜度
// 全域性變數,大小為 10 的陣列 array,長度 len,下標 i。 int array[] = new int[10]; int len = 10; int i = 0; // 往陣列中新增一個元素 void add(int element) { if (i >= len) { // 陣列空間不夠了 // 重新申請一個 2 倍大小的陣列空間 int new_array[] = new int[len*2]; // 把原來 array 陣列中的資料依次 copy 到 new_array for (int j = 0; j < len; ++j) { new_array[j] = array[j]; } // new_array 複製給 array,array 現在大小就是 2 倍 len 了 array = new_array; len = 2 * len; } // 將 element 放到下標為 i 的位置,下標 i 加一 array[i] = element; ++i; }
思考:
當i>=len時遍歷array,將其成員賦值給new_array,所以時間複雜度為n,即最壞情況時間複雜度為O(n)
當i<=len時,直接插入在i位置處,所以時間複雜度為1,即最好情況時間複雜度為O(1)
而由於每次O(len)的出現都跟著len次O(1),是前後連貫的,因而將O(len)平攤到前len次上,得出平攤複雜度是O(1)