逐步學習什麼是遞迴?通過使用場景來深入認識遞迴。

混沌傳奇發表於2018-02-27

遞迴演算法

我們先來看一下定義。遞迴演算法,是將問題轉化為規模縮小的同類問題的子問題,每一個子問題都用一個同樣的演算法去解決。一般來說,一個遞迴演算法就是函式呼叫自身去解決它的子問題。

遞迴演算法的特點:

在函式過程中呼叫自身。在遞迴過程中,必須有一個明確的條件判斷遞迴的結束,既遞迴出口。遞迴演算法簡潔但效率低,通常不作為推薦演算法。

上面這些是百度百科的解釋,講的也是十分明確,大家配合例項來細細琢磨。


求一個數的n次方(n>
=0)

我們拿到問題的時候,我們按照定義的說明,可以先將規模縮小到同類的子問題。比如:求一個數的n次方,就是把n個相同的數相乘的積。計算過程就是第一個數乘以第二個數,再把這兩個數的乘積跟第三個數相乘,依次把上一次的乘積跟下一個數相乘,直到乘到第n個數。規律就是把這一次的乘積和下一個數相乘。遞迴的目的就是把一個大問題拆分成若干個用同樣邏輯解決的小問題。這裡我們用遞迴的解法就是:

//非尾遞迴function com2 (num, n) { 
if(n<
=0){
return 0;

}else if(n==1){
return num;

} return num*arguments.callee(num, n-1)
}// 用法:// com2(12,2)// >
輸出144
複製程式碼

我們可以看到函式com2第一個引數是num(數值),第二個引數是n(數值的n次方)。函式體內部判斷如果n<
=0
,則返回0;如果n==1則返回數值num;這裡這兩個條件就是遞迴函式的遞迴出口。n<
=0
是為了處理求一個數的<
=0
次方時的異常處理。n==1是為了退出遞迴迴圈。

最後面的return num*arguments.callee(num, n-1)就是我們的主遞迴邏輯了。把一個數乘以下一個數,直到n==1時退出遞迴迴圈,並且把最後的乘積一層層返回出來。

//尾遞迴function com2 (num, n) { 
if(n<
=0){
return arguments[2] || 0;

} var product = num;
if(arguments[2]){
product = num*arguments[2];

} return arguments.callee(num, n-1, product)
}// 用法:// com2(12,2)// >
輸出144
複製程式碼

尾遞迴。為什麼我要說尾遞迴,上面我們也說了,遞迴演算法簡潔但效率低,那麼有沒有優化方案呢?答案是“有”。那就是尾遞迴。

什麼是尾遞迴?
尾遞迴的判斷標準是函式執行【最後一步】是否只呼叫自身。通常會把上一個方法的返回值當作引數傳給下一個遞迴方法。
如上面程式碼:return num*arguments.callee(num, n-1)就不是尾遞迴,因為最後執行的是num*函式自身呼叫,而不是純粹的呼叫函式自身。return arguments.callee(num, n-1, product)就是純粹的呼叫函式自身的尾遞迴。

那麼為什麼遞迴效率低呢?

眾所周知,遞迴非常消耗記憶體,因為需要同時儲存很多的呼叫幀,這樣,就很容易發生“棧溢位”。

尾遞迴的作用就是保留一個呼叫記錄。這樣速度就可以提升上來了。

遺憾的是,當前提供尾遞迴優化的瀏覽器比較少。


實際應用場景

將陣列obj格式:

var obj = [    {id:1, parent: null
}, {id:2, parent: 1
}, {id:3, parent: 2
},];
複製程式碼

轉換為obj2格式:

var obj2 = { 
obj: {
id: 1, parent: null, child: {
id: 2, parent: 1, child: {
id: 3, parent: 2
}
}
}
}複製程式碼
程式碼實現:
var obj2 = {
};
function createObj2(obj, child){
if(child.parent){
if(obj.obj){
createObj2(obj.obj, child);

}else{
if(obj.id === child.parent){
obj.child = {
id: child.id, parent: child.parent,
}
}else{
if(obj.child){
createObj2(obj.child, child);

}else{
console.log('obj2未匹配到對應的parent對應關係')
}
}
}
}else{
obj.obj = {
id: child.id, parent: child.parent, child: {
}
}
}
}obj.forEach((item, item_i) =>
{
createObj2(obj2, item)
})console.log('obj2:', obj2)複製程式碼

結語

一般樹狀結構的都可以使用遞迴查詢,比如:
查詢地區,樹狀的選單等等。

注意:遞迴比普通的演算法耗記憶體,深度遞迴的函式可能會因為返回堆疊溢位而執行失敗。在適當的時候用遞迴,不要濫用遞迴。

備註:喜歡這篇文章的話,可以關注我,我會持續更新技術文章到我的掘金主頁,喜歡交流技術的朋友可以加我的專業前端QQ交流群。
QQ群號:583575104 或 366420656

來源:https://juejin.im/post/5a94d6476fb9a06348538871?utm_medium=fe&utm_source=weixinqun

相關文章