時間複雜度和空間複雜度,咱們在大學裏的算法與數據結構課程中已經學習過,這回根據項目工做中整理一下,這個估計只是一個粗略的估計分析,並非一個準確的估計分析。算法
一、學習時間複雜度和空間複雜度是頗有必要的,這個屬於算法與數據結構的範疇,學這個是爲了解決代碼中的「快」和「省」的問題。這樣才能使你的代碼運行效率更高,佔用空間更小。代碼執行效率須要經過複雜度分析。數組
二、數據規模的大小會影響到複雜度分析。好比排序,若是是一個有序的數組,執行效率會更高;若是數據量不多的時候,這個算法看不出性能上差異。數據結構
三、好比說不一樣物理機環境不同,好比i3,i5,i7的cpu等等,運行內存1G,2G,4G,8G等等;時間上確定有差異。函數
咱們來看個簡單的例子,一個循環累加器。性能
function total(n) { var sum = 0; //t for (var i = 0; i < n; i++) { // nt sum += i; //nt } return sum; // t }
分析:假設每一行代碼執行耗時都同樣,爲t,這樣整個代碼總執行時間爲(t+nt+nt+t)=(2n+2)t。學習
咱們再來看一個栗子:優化
function total(n) { var sum = 0; // t for (var i = 0; i < n; i++) { //nt for (var j = 0; j < n; j++) { //n*n*t sum = sum + i + j; //n*n*t } } return sum; //t }
分析:假設每一行代碼執行耗時都同樣,爲t,這樣整個代碼總執行時間爲(t+nt+nnt2+t)=(2nn+n+2)t。spa
從數學角度來看,咱們能夠得出個規律:代碼的總執行時間 T(n) 與每行代碼的執行次數成正比.code
因此上邊兩個函數的執行時間能夠標記爲 T(n) = O(2n+2) 和 T(n) = O(2n*n+n+2)。這就是大 O 時間複雜度表示法,它不表明代碼真正的執行時間,而是表示代碼隨數據規模增加的變化趨勢,簡稱時間複雜度。blog
並且當 n 很大時,咱們能夠忽略常數項,只保留一個最大量級便可。因此上邊的代碼執行時間能夠簡單標記爲 T(n) = O(n) 和 T(n) = O(n2)。
在分析的時候,只須要關心哪一個代碼塊循環次數最多的那段代碼,好比說剛纔的第一個例子,循環最多的代碼塊是這兩行,循環都是n次。
for (var i = 0; i < n; i++){ sum += i; }
執行了n次,因此事件複雜度就是O(n)。
function total(n) { // 第一個 for 循環 var sum1 = 0; for (var i = 0; i < n; i++) { for (var j = 0; j < n; j++) { sum1 = sum1 + i + j; } } // 第二個 for 循環 var sum2 = 0; for(var i=0;i<1000;i++) { sum2 = sum2 + i; } // 第三個 for 循環 var sum3 = 0; for (var i = 0; i < n; i++) { sum3 = sum3 + i; } return {sum1:sum1, sum2: sum2, sum3:sum3} }
分別分析每一段循環時間複雜度,取他們最大的量級決定整段代碼複雜度。
第一段,時間複雜度 O(n2)。
第二段,時間複雜度能夠忽略,循環執行了 1000 次,是個常數量級,儘管對代碼的執行時間會有影響,可是當 n 無限大的時候,就能夠忽略。
第三段,時間複雜度O(n)。
綜上所述,因此上述代碼的時間複雜度爲 O(n2)。
舉個例子:
function f(i) { var sum = 0; for (var j = 0; j < i; j++) { sum += i; } return sum; } function total(n) { var res = 0; for (var i = 0; i < n; i++) { res = res + f(i); // 調用 f 函數 } }
分析一下:total方法時間複雜度O(n),f方法的時間複雜度O(n)。
因此整段代碼的時間複雜度O(n2)。
最高量級的複雜度,效率是遞減的
如上圖能夠粗略的分爲兩類,多項式量級和非多項式量級。其中,非多項式量級只有兩個:O(2n) 和 O(n!) 對應的增加率以下圖所示
當數據規模 n 增加時,非多項式量級的執行時間就會急劇增長,因此,非多項式量級的代碼算法是很是低效的算法。
O(1) 只是常量級時間複雜度表示法,並非代碼只有一行,舉個例子:
function total() { var sum = 0; for(var i=0;i<100;i++) { sum += i; } return sum; }
雖然有這麼多行,即便 for 循環執行了 100 次,可是代碼的執行時間不隨 n 的增大而增加,因此這樣的代碼複雜度就爲 O(1)。
(1)對數階時間複雜度的常見代碼以下:
function total1(n) { var sum = 0; var i = 1; while (i <= n) { sum += i; i = i * 2; } } function total2(n) { var sum = 0; for (var i = 1; i <= n; i = i * 2) { sum += i; } }
上面函數total1和total2有一個相同點:變量 i 從 1 開始取值,每循環一次乘以 2,當大於 n 時,循環結束。
因此真正循環了x次。2x =n;,因此 x = log2n。
因此上面兩個函數時間複雜度都是 O(log2n)。
(2)咱們在舉個例子:
function total1(n) { var sum = 0; var i = 1; while (i <= n) { sum += i; i = i * 3; } } function total2(n) { var sum = 0; for (var i = 1; i <= n; i = i * 3) { sum += i; } }
同理可知:這兩個函數的時間複雜度爲 O(log3n) 。
因爲咱們能夠忽略常數,也能夠忽略對數中的底數,因此在對數階複雜度中,統一表示爲 O(logn);那 O(nlogn) 的含義就很明確了,時間複雜度 爲O(logn) 的代碼執行了 n 次。
舉個例子:
function total(m,n) { var sum1 = 0; for (var i = 0; i < n; i++) { sum1 += i; } var sum2 = 0; for (var i = 0; i < m; i++) { sum2 += i; } return sum1 + sum2; }
由於咱們沒法評估 m 和 n 誰的量級比較大,因此就不能忽略掉其中一個,這個函數的複雜度是有兩個數據的量級來決定的,因此此函數的時間複雜度爲 O(m+n);那麼 O(m*n) 的時間複雜度相似。
空間複雜度的話和時間複雜度相似推算。 所謂空間複雜度就是表示算法的存儲空間和數據規模之間的關係。
舉個例子:
function initArr(n) { var arr = []; for (var i = 0; i < n; i++) { arr[i] = i; } }
時間複雜度的推算,忽略掉常數量級,每次數組賦值都會申請一個空間存儲變量,因此此函數的空間複雜度爲 O(n)。
常見的空間複雜度只有 O(1)、O(n)、O(n2)。其餘的話不多會用到。
function total(n) { var sum = 0; for (var i = 1; i <= n; i++) { sum += i; } return sum; }
這段代碼咱們很容易知道時間複雜度 O(n)。
可是我想把複雜度降一降,下降到常數階O(1)。
其實求和怎麼求呢?等比數列,直接用公式,這就說明了數學好的人,算法應該高level點。
function total(n) { var sum = n*(n+1)/2 return sum; }
上面函數的時間複雜度僅僅爲 O(1),在數據規模比較龐大的時候,下面的函數是否是明顯比上面的函數運算效率更高呢。
分析算法執行效率與數據規模之間的增加關係,能夠粗略的表示,越高階複雜度的算法,執行效率越低。
複雜度學習以後,有時候能夠避免寫出效率低的代碼。