時間複雜度、遞歸、尾調用— (讀文筆記)

輸出

斐波那契數列的四種寫法算法

讀參考文章列表

  1. 算法複雜度中的O(logN)底數是多少
  2. 從斐波那契數列談談代碼的性能優化
  3. 冰與火之歌:時間與空間複雜度
  4. 看動畫輕鬆理解遞歸與動態規劃
  5. JavaScript調用棧、尾遞歸和手動優化
  6. 數據結構與算法公開課

問本身幾個問題

  1. 算法複雜度中的O(logN)底數是多少, log2N 和 log10N 有區別麼?
  2. 複習時間複雜度、空間複雜度、時間複雜度從小到大
  3. 時間複雜度級數
  4. 循環與級數的關係
  5. 分治、遞歸,遞歸的時間複雜度
  6. 從一個數組中找出最大的兩個數
  7. 什麼是動態規劃,時間複雜度多少
  8. 尾調用和普通調用有啥不同

問題解答

1,常底數是無所謂的,logaN/logbN = logab, 是一個常數數組

2,時間複雜度:
代碼段執行次數累加性能優化

空間複雜度:
除了輸入自己所佔的空間以外,用於計算的所必須的空間量數據結構

時間複雜度從小到大
O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)
因此,O(n) 可否優化 O(logn)?優化關鍵閉包

3,Ep12 時間複雜度級數
算數級數,與末項平房同階,
1+2+3+...n = O(n^2)ide

冪方級數,比冪次高出一階:
1+2^2+3^2+4^2 +... n^2 = O(n^3);
1+2^3+3^3+4^3 +... n^3= O(n^4);
1+2^4+3^4+4^4+... n^4 = O(n^5); 函數

幾何級數(a>1):與末項同階
a^0+a^1+a^2+... a^n = (a^(n+1) - 1)/(a-1) = O(a^n)
1+2+4+8 +... 2^n = 2^(n+1) -1 = O(2^n)post

4,循環與級數的關係性能

for(let i = 0; i < n; i++)
    for(let j = 0; i < n; j++)  座標軸造成正方形,O(n^2)
     
    for(let i = 0; i < n; i++)
    for(let j = 0; i < j; j++)  座標軸造成三角形,O(n^2)

5,冰與火之歌:時間與空間複雜度優化

分治:將原問題分解爲若干個規模較小但相似於原問題的子問題(Divide),「遞歸」的求解這些子問題(Conquer),而後再合併這些子問題的解來創建原問題的解。

遞歸算法是一種直接或者間接調用自身函數或者方法的算法。通俗來講,遞歸算法的實質是把問題分解成規模縮小的同類問題的子問題,而後遞歸調用方法來表示問題的解。它有以下特色:

1. 一個問題的解能夠分解爲幾個子問題的解 
2. 這個問題與分解以後的子問題,除了數據規模不一樣,求解思路徹底同樣 
3. 存在遞歸終止條件,即必須有一個明確的遞歸結束條件,稱之爲遞歸出口

遞歸算法的世界複雜度,得分好幾種
第一種:
遞歸中進行一次遞歸調用的複雜度分析,如:二分查找法
無論怎麼樣,最多調用了一次遞歸調用而已,這時候時間複雜度看何時跳出遞歸

第二種:
遞歸中進行屢次遞歸調用的複雜度分析
好比說斐波拉契數列,屢次調用自身
因此時間複雜度等於遞歸樹中節點數總和,就是代碼計算的調用次數。

T(n) = 各層遞歸實例所需時間之和
= O(1) * (2^0 + 2 ^1 + ...2^n)
= O(1) * (2^(n+1) - 1)
= O(2^n)

空間複雜度:
一個程序執行時除了須要存儲空間和存儲自己所使用的指令、常數、變量和輸入數據外,還須要一些對數據進行操做的工做單元和存儲一些爲現實計算所需信息的輔助空間。

6,從一個數組中找出最大的兩個數
算法一
先遍歷一遍,找出最大值的位置,x1, 時間複雜度爲 n-1
再遍歷一遍,從剩下的n-2個數中,找最大值,時間複雜度爲 n-2
總共 O(2n-3) = O(n)

算法二
x1是小值,x2是大數
若是值比x2大,替換x2
若是 x1<num<x2, 替換x1
時間複雜度O(n)

算法三
左邊找最大L1,右邊找最大 R1
L1<R1, 要麼找右邊邊第二大
else, 要麼找左邊第二大

7,看動畫輕鬆理解遞歸與動態規劃
動態規劃能解決的問題分治策略確定能解決,只是運行時間長了。所以,分治策略通常用來解決子問題相互對立的問題,稱爲標準分治,而動態規劃用來解決子問題重疊的問題。

將「動態規劃」的概念關鍵點抽離出來描述就是這樣的:

1.動態規劃法試圖只解決每一個子問題一次
2.一旦某個給定子問題的解已經算出,則將其記憶化存儲,以便下次須要同一個子問題解之時直接查表。

8.JavaScript調用棧、尾遞歸和手動優化
尾調用:
就是一個函數執行的最後一步是將另一個函數調用並返回。

通常來講,若是方法a調用方法b, 那麼b放到棧頂,棧指針指向棧頂, 當前幀是b, 調用幀是a,

當函數B執行完成後,還須要將執行權返回A,那麼函數A內部的變量,調用函數B的位置等信息都必須保存在調用幀A中。否則,當函數B執行完繼續執行函數A時,就會亂套。
那麼如今,咱們將函數B放到了函數A的最後一步調用(即尾調用),那還有必要保留函數A的棧幀麼?固然不用,由於以後並不會再用到其調用位置、內部變量。所以直接用函數B的棧幀取代A的棧幀便可。固然,若是內層函數使用了外層函數的變量,那麼就仍然須要保留函數A的棧幀,典型例子便是閉包。

總得來講,若是全部函數的調用都是尾調用,那麼調用棧的長度就會小不少,這樣須要佔用的內存也會大大減小。這就是尾調用優化的含義。

// 尾調用錯誤示範1.0
function f(x){
  let y = g(x);
  return y;
}

// 尾調用錯誤示範2.0
function f(x){
  return g(x) + 1;
}
// 尾調用錯誤示範3.0
function f(x) {
  g(x); // 這一步至關於g(x) return undefined
}

1.0最後一步爲賦值操做,2.0最後一步爲加法運算操做,3.0隱式的有一句return undefined
相關文章
相關標籤/搜索