複雜度也稱爲漸進複雜度,包括漸進時間複雜度和漸進空間複雜度,描述算法隨數據規模變化而逐漸變化的趨勢。複雜度分析是評估算法好壞的基礎理論方法,因此掌握好複雜度分析方法是頗有必要的。算法
首先,學習數據結構是爲了解決「快」和「省」的問題,那麼如何去評估算法的速度快和省空間呢?這就須要掌握時間和空間複雜度分析。同一段代碼運行在不一樣環境、不一樣配置機器、處理不一樣量級數據…效率確定不會相同。時間複雜度和空間複雜度是不運行代碼,從理論上粗略估計算法執行效率的方法。時間複雜度通常用O來表示,以下例子:計算1,2,3…n的和。CPU執行每行代碼時間很快,假設每行執行時間都同樣爲unit_time,第2行爲一個unit_time,第三、4行都執行了n遍,那麼下面這段代碼執行的耗時時間能夠這麼計算:(1+2*n) * unit_time。數組
1 public int sum(int n) { 2 int sum = 0; 3 for (int i = 1; i <= n; i++) { 4 sum = sum + i; 5 } 6 return sum; 7 }
相似的再看一個例子:數據結構
1 public int sum(int n) { 2 int sum = 0; 3 int i = 1; 4 int j; 5 for (; i <= n; i++) { 6 j = 1; 7 for (; j <= n; j++) { 8 sum = sum + i * j; 9 } 10 } 11 return sum; 12 }
第二、三、4行分別執行執行了一次,時間爲3unit_time,第五、6兩行循環了n次爲2n * unit_time,第七、8兩行執行了n*n次爲(n²) * unit_time,因此總的執行時間爲:(2n²+2n+3) * unit_time學習
能夠看出來,全部代碼執行時間T(n)與每行代碼執行次數成正比。能夠用以下公式來表示:spa
T(n) = O(f(n))code
T(n)表示代碼的執行時間;blog
n表示數據規模大小;it
f(n)表示每行代碼執行的次數和,是一個表達式;class
O表示執行時間T(n)和f(n)表達式成正比效率
那麼上面兩個時間複雜度能夠表示爲:
T(n) = O(1+2*n) 和 T(n) = O(2n²+2n+3)
實際上O並不表示具體的執行時間,只是表示代碼執行時間隨數據規模變化的趨勢,因此時間複雜度其實是漸進時間複雜度的簡稱。當n很大時,係數對結果的影響很小能夠忽略,上面兩個例子的時間複雜度能夠粗略簡化爲:
T(n) = O(n) 和 T(n) = O(n²)
由於時間複雜度是表示的一種趨勢,因此經常忽略常量、低階、係數,只須要最大階量級就能夠了。
上面例子能夠看出,複雜度忽略了低階、常量和係數,因此執行最多的那一段最能表達時間複雜度的趨勢。
仍是上面的例子,總的時間複雜度等於各部分代碼時間複雜度的和,求和以後再用最能表達趨勢的項來表示整段代碼的時間複雜度。
上面第二段代碼,j 循環段嵌套在 i 循環內部,因此 j 循環體內的時間複雜度等於單獨 i 的時間複雜度乘以單獨 j 的時間複雜度。
常見的複雜度有如下幾種
能夠這麼來理解:若是一段代碼有1000或10000行甚至更多,行數是一個常量,不會隨着數據規模增大而變化,咱們就認爲時間複雜度爲一個常量,用O(1)表示。
這幾種複雜度效率曲線比較
模擬一個數組動態擴容例子,若是數組長度夠,直接往裏面插入一條數據;反之,將數組擴充一倍,而後往裏面插入一條數據:
1 int[] arr = new int[10]; 2 int len = arr.length; 3 int i = 0; 4 public void add(int item) { 5 if (i >= len) { 6 int[] new_arr = new int[len * 2]; 7 for (int i = 0; i < len; i++) { 8 new_arr[i] = arr[i]; 9 } 10 arr = new_arr; 11 len = arr.length; 12 } 13 arr[i] = item; 14 i++; 15 }
最好狀況下某個算法的時間複雜度。最好狀況下,數組空間足夠,只須要執行插入數據就能夠了,此時時間複雜度是O(1)。
最壞狀況下某個算法的時間複雜度。最壞狀況下數組滿了,須要先申請一個空間爲原來兩倍的數組,而後將數據拷貝進去,此時時間複雜度爲O(n)。通常狀況下咱們說算法複雜度就是指的最壞狀況時間複雜度,由於算法時間複雜度不會比最壞狀況複雜度更差了。
最好時間複雜度和最壞時間複雜度都是極端狀況下的時間複雜度,發生的機率並不算很大。平均時間複雜度是描述各類狀況下平均的時間複雜度。上面的動態擴容例子將1到n+1次爲一組來分析,前面n次的時間複雜度都是1,第n+1次時間複雜度是n,將一個數插入數組裏的1 至 (n+1)個位置機率都爲1/(n+1),因此平均時間複雜度爲:
O(n) = (1 + 1 + 1 + …+n)/(n+1) = O(1)
對一個數據結構進行一組連續的操做中,大部分狀況下時間複雜度都很低,只有個別狀況下時間複雜度比較高,並且這些操做之間存在先後連續的關係。而且和這組數據類型的狀況循環往復出現,這時候能夠將這一組數據做爲一個總體來分析,看看是否能夠將最後一個耗時的操做複雜度均攤到其餘的操做上,若是能夠,那麼這種分析方法就是均攤時間複雜度分析法。上面的例子來說,第n+1次插入數據時候,數組恰好發生擴容,時間複雜度爲O(n),前面n次恰好將數組填滿,每次時間複雜度都爲O(1),此時能夠將第n+1次均攤到前面的n次上去,因此總的均攤時間複雜度仍是O(1)。
類比時間複雜度,以下代碼所示,第2行申請了一個長度爲n的數據,第三行申請一個變量i爲常量能夠忽略,因此空間複雜度爲O(n)
1 public void init(int n) { 2 int[] arr = new int[n]; 3 int i = 0; 4 for (; i < n; i++) { 5 arr[i] = i + 1; 6 } 7 }
通常狀況下,一個程序在機器上執行時,除了須要存儲程序自己的指令、常數、變量和輸入數據外,還須要存儲對數據操做的存儲單元,若輸入數據所佔空間只取決於問題自己,和算法無關,這樣只須要分析該算法在實現時所需的輔助單元便可。若算法執行時所需的輔助空間相對於輸入數據量而言是個常數,則稱此算法爲原地工做,空間複雜度爲O(1)。