在編程中,一段代碼的執行效率實際上很難估算和預測,其主要受到以下幾個方面的影響:javascript
1.算法依據的數學基礎。java
2.編譯器產生的代碼質量和語言的執行效率。算法
3.問題的輸入規模。編程
4.硬件的執行速度。函數
一般狀況下,問題的輸入規模和算法的數學基礎是編碼人員須要考慮的條件。時間複雜度是一個用來描述算法執行效率的重要標準。 性能
在理解時間複雜度以前,你應該先了解什麼叫作算法的時間頻度,所謂時間頻度便是一個算法解決問題所消耗的時間。可是通常狀況下,一個算法解決問題消耗的時間一般與輸入值有關,例如咱們輸入一個整數,找到比它小的全部正偶數,代碼以下:編碼
let n = 10; for (var i = 0; i < n; i++) { if (i%2==0) { console.log(i); } }
上面代碼,當輸入n爲10時,循環會執行10次,若是時間頻度t,則當輸入n爲20時,時間頻度爲2t。時間複雜度是用來描述隨着問題規模n的變化時間頻度t的變化規律。下面是一段更加數學風格的描述:spa
通常狀況下,算法中基本操做重複執行的次數是問題規模n的某個函數,用T(n)表示,如有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數。記做T(n)=O(f(n)),稱O(f(n)) 爲算法的漸進時間複雜度,簡稱時間複雜度。code
計算一個算法的時間複雜度時,咱們能夠將算法分解爲逐條語句,計算每條語句的時間複雜度後再進行累加,以下代碼的做用是對輸入進行求累加:遞歸
let n = 10; let res = 0; //1 for (var i = n; i > 0; i--) { //1+(n+1)+(n+1) res = i+res; //n } console.log(res);//1
當n輸入爲10時,時間頻度爲1+1+n+1+n+1+n+1 = 3n+5。設算法的時間複雜度函數爲f(n),(3n+5)/f(n)當n趨於無窮大時,上式能夠簡化爲3n/f(n),取f(n)=n,上次結果爲非零常數,所以此算法的時間複雜度爲f(n)=n,記作O(n)。
當算法的執行時間頻度和n無關時,算法的時間複雜度爲O(1),這是時間複雜度最小的函數,可是須要注意,時間複雜度小並不能說明算法執行耗費的時間短,好比一萬行代碼每行只執行一次的算法時間複雜度也爲O(1)。
常見的算法時間複雜度由小到大以此爲:
Ο(1)<Ο(log²n)<Ο(n)<Ο(nlog²n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)
其中O(log² n)是除了O(1)外時間複雜度最小的函數,例如以下代碼:
let n = 10; var i = 1; while(i<n){//2^t<n t<log2(n) t爲時間頻度 i = i * 2; tip++; }
上面的時間頻度爲 1+1+log2(n)+log2(n)+log2(n),去掉常數項後爲3log2(n),時間複雜度爲O(log2(n))。若是將上面的代碼在加一層循環,則時間複雜度會變爲O(nlog3(n)):
let n = 10; for (var i = 0; i < n; i++) {// 1+n+1+n+1 var j = 1; //n while(j<n){//[3^t<n t<log3(n)]n t爲時間頻度 j = j*3; } }
經過上面的示例,也很容易能夠看出,循環層數的增多會劇烈的增長算法的時間複雜度,若是在遞歸函數中使用循環,則很容易產生時間複雜度爲O(n!)的代碼,從數學上看,這種代碼隨着輸入複雜度的增長性能會急劇降低,在使用遞歸加循環時,仍是要多多注意,示例代碼以下:
function func(n) { //n if (n<0) { return; } var i = 0; //n for (; i < n; i++) { //n*(n-1)*(n-2)...*1 console.log("tip"); } func(--i); } func(10);
上面示例的JavaScript代碼當傳入n爲150時的耗時已經和正常循環10000次的相同。