1、大 O 複雜度表示法
1 int cal(int n) { 2 int sum = 0; 3 int i = 1; 4 int j = 1; 5 for (; i <= n; ++i) { 6 j = 1; 7 for (; j <= n; ++j) { 8 sum = sum + i * j; 9 } 10 } 11 }
第 2、3、4 行代碼,每行都需要 1 個 unit_time 的執行時間,第 5、6 行代碼循環執行了 n 遍,需要 2n * unit_time 的執行時間,第 7、8 行代碼循環執行了 n2遍,所以需要 2n2* unit_time 的執行時間。所以,整段代碼總的執行時間 T(n) = (2n2+2n+3)*unit_time。用大 O 表示時間複雜度T(n) = O(n2)。
規律:所有代碼的執行時間 T(n) 與每行代碼的執行次數 n 成正比。
公式:
含義:T(n) 表示代碼執行的時間;n 表示數據規模的大小;f(n) 表示每行代碼執行的次數總和。公式中的 O,表示代碼的執行時間 T(n) 與 f(n) 表達式成正比。
定義:大 O 時間複雜度實際上並不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增長的變化趨勢,所以,也叫作漸進時間複雜度(asymptotic time complexity),簡稱時間複雜度。當 n 很大時,你可以把它想象成 10000、100000。而公式中的低階、常量、係數三部分並不左右增長趨勢,所以都可以忽略。我們只需要記錄一個最大量級就可以了,如果用大 O 表示法表示這段代碼的時間複雜度,就可以記爲: T(n) = O(n2)。
2、複雜度分析法則
1)單段代碼看高頻:比如循環。
2)多段代碼取最大:比如一段代碼中有單循環和多重循環,那麼取多重循環的複雜度。
3)嵌套代碼求乘積:比如遞歸、多重循環等
4)多個規模求加法:比如方法有兩個參數控制兩個循環的次數,那麼這時就取二者複雜度相加。
3、幾種常見時間複雜度實例分析
多項式階:隨着數據規模的增長,算法的執行時間和空間佔用,按照多項式的比例增長。包括,O(1)(常數階)、O(logn)(對數階)、O(n)(線性階)、O(nlogn)(線性對數階)、O(n2)(平方階)、O(n3)(立方階)
非多項式階:隨着數據規模的增長,算法的執行時間和空間佔用暴增,這類算法性能極差。包括,O(2n)(指數階)、O(n!)(階乘階)
案例:
1、O(1) 常數階:一般情況下,只要算法中不存在循環語句、遞歸語句,即使有成千上萬行的代碼,其時間複雜度也是Ο(1)。
1 int i = 8; 2 int j = 6; 3 int sum = i + j;
2、O(logn) 對數階:
1 i=1; 2 while (i <= n) { 3 i = i * 2; 4 }
第三行代碼是循環執行次數最多的,只要能計算出這行代碼被執行了多少次,就能知道整段代碼的時間複雜度。從代碼中可以看出,變量 i 的值從 1 開始取,每循環一次就乘以 2。當大於 n 時,循環結束。還記得我們高中學過的等比數列嗎?實際上,變量 i 的取值就是一個等比數列。如果我把它一個一個列出來,就應該是這個樣子的:
所以,我們只要知道 x 值是多少,就知道這行代碼執行的次數了。通過 2x=n 求出x=log2n,所以,這段代碼的時間複雜度就是 O(log2n)。但是當n無窮大的時候,忽略對數的“底”,統一表示爲 O(logn)。
3、O(nlogn) 線性對數階: 如果一段代碼的時間複雜度是 O(logn),我們循環執行 n 遍,時間複雜度就是 O(nlogn) 了。比如,歸併排序、快速排序的時間複雜度都是 O(nlogn)。
4、O(m+n)、O(m*n) :代碼的複雜度由兩個數據的規模來決定。如下面的代碼時間複雜度就是 O(m+n)。
1 int cal(int m, int n) { 2 int sum_1 = 0; 3 int i = 1; 4 for (; i < m; ++i) { 5 sum_1 = sum_1 + i; 6 } 7 8 int sum_2 = 0; 9 int j = 1; 10 for (; j < n; ++j) { 11 sum_2 = sum_2 + j; 12 } 13 14 return sum_1 + sum_2; 15 }