T(n)
和每行代碼的執行次數 n
成正比。T(n) = O (f(n))
- 只關注循環次數最多的一段代碼.
- 加法法則:總複雜度等於量級最大的那一段代碼。
- 乘法法則:嵌套代碼的複雜度等於內外代碼複雜度之積。
\(O(1)\)算法
int i = 1 ; int j = 2 ; int sum = i + j ;
常量級時間複雜度的表示方法,即使有 3 行,也是 O(1) ,並不是 O(3) 。通常狀況下,只要不存在循環、遞歸,複雜度都爲 O(1) ,與代碼量無關。數組
\(O(logn)、O(nlogn)\)數據結構
int i = 1 ; while ( i <= n ) { i = i * 2 ; }
這段代碼的複雜度爲 \(O(log_2n)\),不一樣底數的對數能夠互相轉換,係數能夠省略,因此統一表示爲 \(O(logn)\)。
\(O(nlogn)\) 表示把 \(O(logn\)) 的代碼循環執行n遍。數據結構和算法
\(O(n+m)、O(n*m)\)學習
T1(n) + T2(m) = O (f(n) + g(m)) T1(n) * T2(m) = O (f(n) * g(m))
當有多個數據規模,表示複雜度時不能省略。spa
void print(int n) { int i = 0; int[] a = new int[n]; for (i; i <n; ++i) { a[i] = i * i; } }
int[] a = new int[n]
申請了大小爲 n 的 int 類型數組,忽略常量階的空間申請,因此上述代碼空間複雜度爲 O(n)。public int find(int[] array,int n,int x) { int p = -1 ; for(int i = 0 ; i < n ; ++i) { if(array[i] == x) { p = i ; break ; } } return p ; } /** 上述代碼的做用是返回數組中 x 出現的位置。 最好:第一個就是要找-> O(1) 最壞:遍歷整個數組沒有找到該元素-> O(n) **/
最理想狀況下,執行這段代碼的時間複雜度。code
最糟糕狀況下,執行這段代碼的時間複雜度。遞歸
分析上述代碼,在長度爲n的數組中查詢x的位置,有 n+1 種狀況,分別爲數組的 0 到 n-1 位置上和不在數組中,把每種狀況下,須要遍歷的元素個數累加起來,除以 n+1 ,獲得須要遍歷元素個數的平均值。資源
\[ \frac{1+2+3+...+n+n}{n+1}\quad=\quad\frac{n(n+3)}{2(n+1)} \]
忽略常量、係數、低階,簡化後時間複雜度爲O(n);結果雖然正確,可是這樣計算沒有考慮每種狀況發生的機率,正確計算過程以下:
\[ 1*\frac{1}{2n}+2*\frac{1}{2n}+...+n*\frac{1}{2n}+n*\frac{1}{2}=\frac{3n+1}{4} \]
此結果是加權平均值,平均時間複雜度即加權平均時間複雜度,簡化後得 O(n)。
int[] array = new int[n]; int count = 0; void insert(int val) { if (count == array.length) { int sum = 0; for (int i = 0; i < array.length; ++i) { sum = sum + array[i]; } array[0] = sum; count = 1; } array[count] = val; ++count; }
這段代碼實現了一個往數組中插入數據的功能。當數組滿了以後,即 count == array.length 時,用 for 循環遍歷數組求和,將求和以後的 sum 值放到數組的第一個位置,而後從第二個 位置開始插入數據。若是數組一開始就有空閒空間,則直接將數據插入數組
分析以上代碼,得出:
\[ 1*\frac{1}{n+1}+1*\frac{1}{n+1}+...+n*\frac{1}{n+1}=>O(1) \]
均攤時間複雜度是用攤還分析法得出的:
例如上述代碼中,每一次 O(n) 的插入操做,都會緊跟 n-1 個 O(1) 的插入操做,把耗時多的操做均攤到接下來 O(1) 的操做上,那麼這一組操做的均攤時間複雜度就是 O(1)
對一個數據結構進行一組連續操做中,大部分狀況下時間複雜度都很低,只有個別狀況下時間複雜度比較高,並且這些操做之間存在先後連貫的時序關係,這個時候,咱們能夠將這一組操做放在一塊兒分析,看是否能將較高時間複雜度那次操做的耗時,平攤到其餘那些時間複雜度比較低的操做上。
在可以應用均攤時間複雜度分析的場合,通常均攤時間複雜度就等於最好狀況時間複雜度