這是我參與8月更文挑戰的第10天,活動詳情查看: 8月更文挑戰算法
業務代碼開發多了,其實就是在寫 if-else數組
簡單來講,遞歸就是在函數執行體內部調用自身的行爲,這種方式有時可讓複雜的算法實現變得簡單,如計算斐波那契數或階乘。
但使用遞歸也有一些潛在的問題須要注意:好比缺乏或不明確遞歸的終止條件會很容易形成用戶界面的卡頓,同時因爲遞歸是一種經過空間換時間的算法,其執行過程當中會入棧保存大量的中間運算結果,它對內存的開銷將與遞歸次數成正比,因爲瀏覽器都會限制JavaScript的調用棧大小,超出限制遞歸執行便會失敗。瀏覽器
任何遞歸函數均可以改寫成迭代的循環形式,雖然循環會引入自身的一些性能問題,但相比於長時間執行的遞歸函數,其性能開銷仍是要小不少的。以歸併排序爲例:緩存
// 遞歸方式實現歸併排序
function merge(left, right){
const result = []
while(left.length > 0 && right.length > 0) {
// 把最小的先取出來放到結果中
if(left[0] < left[0]){
result.push(left.shift())
} else {
result.push(right.shift())
}
}
// 合併
return result.concat(left).concat(right)
}
// 遞歸函數
function mergeSort(array){
if(array.length == 1) return array
// 計算數組中點
const middle = Math.floor(array.length / 2)
// 分割數組
const left = array.slice(0, middle)
const right = array.slice(middle)
// 進行遞歸合併與排序
return merge(mergeSort(left), mergeSort(right))
}
複製代碼
能夠看出這段歸併排序中,mergeSort()函數會被頻繁調用,對於包含n個元素的數組來講,mergeSort()函數會被調用2n-1次,隨着所處理數組元素的增多,這對瀏覽器的調用棧是一個嚴峻的考驗。改成迭代方式以下:markdown
// 用迭代方式改寫遞歸函數
function mergeSort(array){
if(array.length == 1 ) return array
const len = array.length
const work = []
for(let i = 0 ; i < len; i ++){
work.push([array[i]])
}
// 確保總數組長度爲偶數
if(len & 1) work.push([])
// 迭代兩兩歸併
for(let lim = len; lim > 1; lim = (lim+1)/2) {
for(let j = 0,k = 0; k < lim; j += 1, k += 2){
work[j] = merge(work[k], work[k+1])
}
// 數組長度爲奇數時,補一個空數組
if(lim & 1) work[j] = []
}
return work[0]
}
複製代碼
此處經過迭代實現的mergeSort()函數,其功能上與遞歸方式相同,雖然在執行時間閃過來看可能要慢一些,但它不會收到瀏覽器對JavaScript調用棧的限制。閉包
若是在遞歸過程當中,前一次的計算結果能被後一次計算使用,那麼緩存前一次的計算結果就能有效避免許多重複工做,這樣就能帶來很好的性能提高。好比遞歸常常會處理的階乘操做以下:app
// 計算n的階乘
function factorial(n){
if(n === 0) {
return 1
} else {
return n * fatorial(n-1)
}
}
複製代碼
當咱們要計算多個數的階乘(如二、三、4)時,若是分別計算這三個數的階乘,則函數factorial()總共要被調用12次,其中在計算4的階乘時,會把3的階乘從新計算一遍,計算3的階乘時又會把2的階乘從新計算一遍,能夠看出若是在計算4 的階乘以前,將3的階乘數緩存下來,那麼在計算4的階乘時,遞歸僅須要再執行一次。如此經過緩存階乘計算結果,避免多餘計算過程,本來12次的遞歸調用,能夠減小到5次。
根據這樣的訴求,提供一個有效利用緩存來減小沒必要要計算的解決方案:ide
// 利用緩存避免重複計算
function memoize(func, cache){
const cache = cache || {}
return function(args){
if(!cache.hasOwnProperty(args)){
cache[args]=func(args);
}
return cache[args];
}
}
複製代碼
該方法利用函數閉包有效避免了相似計算屢次階乘時的重複操做,確保只有當一個計算在以前從未發生過期,才產生新的計算值,這樣前面的階乘函數即可改寫爲:函數
// 緩存結果的階乘方案
const memorizeFactorial = memorize(factorial, {'0': 1, '1': 1})
複製代碼
這種方式也存在性能問題,好比函數閉包延長了局部變量的存活期,若是數據量過大又不能有效回收,則容易致使內存溢出。這種方案也只有在程序中有相同參數屢次調用纔會比較省時,因此綜合而言,優化方案還需根據具體使用場景具體考慮。post