1. 程式人生 > >演算法--遞迴

演算法--遞迴

遞迴

函式直接或間接呼叫函式本身。遞迴是一種計算過程,如果其中每一步都要用到前一步或前幾步的結果,稱為遞迴的。用遞迴過程定義的函式,稱為遞迴函式。它解決問題的各個小部分,直到解決最初的大問題。

  • 有限次(必須有出口)可預見性結果中,找到結果與上一次結果之間的關係;
  • 關鍵在於梳理清楚本次結果和上一次結果的關係有哪些方面或是因素;
  • 在演算法的分析中,當一個演算法中包含遞迴呼叫時,其時間複雜度的分析會轉化成為一個遞迴方程的求解。

經典遞迴案例:

示例: 斐波那契數列:1、1、2、3、5、8、13、21、34

F(0) = 0; F(1) = 1; F(n) = F(n-1) + F(n-2)

function fibonacci(n) {
  if(n === 0 || n === 1) {
    return 1
  }
  return fibonacci(n - 1) + fibonacci(n - 2)
}
let fibonacci = (function() {
  let memory = []
  return function(n) {
    return memory[n] = (n === 0 || n === 1) ? 1 : fibonacci(n - 1) + fibonacci(n - 2)
  }
})()

示例: 最大公約數,採用輾轉相除法(歐幾里得演算法)

定理:兩個整數的最大公約數等於其中較小的那個數和兩數相除餘數的最大公約數。

gcd(a, b) = gcd(a, a mod b)

function gcd (a, b) {  
    if (a < b) {
        [a, b] = [b, a]
    }
    if (a % b === 0) {
        return b
    }
    return gcd(b, a % b)
}

其他方式:分解質因數、短除法、更相減損法等

示例:

var menu = {
	children: [{
        path: '/a',
        name: 'a',
        meta: {name: 'a'},
        children: [{
            path: 'a1',
            name: 'a1',
            meta: {name: 'a1'},
            children: [{
                id: 'a11',
                path: 'a11',
                name: 'a11',
                meta: {name: 'a11'},
            }, {
                path: 'a12',
                name: 'a12',
                meta: {name: 'a12'},
            }]
        }, {
            path: 'a2',
            name: 'a2',
            meta: {name: 'a2'},
        }]
    }, {
        path: '/b',
        name: 'b',
        meta: {name: 'b'}
    }]
}

/**
 * @param menu 選單資料
 * @param name 查詢的節點名稱
 */
function getMenuByName (menu, name) {
    if (menu.name === name) {
        return menu
    }
    let childrens = menu.children
    if (childrens && Array.isArray(childrens)) {
        for(let item of childrens) {
            let result = getMenuByName(item, name)
            if(result) return result
        }
    }
}

示例:遞迴DOM

function getElementById(node, id) {
    if (!node) return null
    if (node.id === id) return node
    for (var i = 0, len = node.childNodes.length; i < len; i++) {
        var found = getElementById(node.childNodes[i], id)
        if (found) return found
    }
    return null
}
getElementById(document, 'abc')

注意:

  • 在寫遞迴時,需要先考慮邊界條件,否則會導致棧溢位(RangeError: Maximum call stack size exceeded)
  • ECMAScript 6有尾呼叫優化。如果函式內最後一個操作是呼叫函式,會通過“跳轉指令”而不是“子程式呼叫”來控制!