貌似大部分語言中的遞歸都差很少, 之因此在標題加JS是由於搜了下後感受網上用js來描述這概念的很少, 簡單地說遞歸就是函數調用本身的過程。下面的栗子能夠很直觀地展現遞歸的執行過程:node
function rec(x){ if(x!==1){ console.log(x) rec(x-1) console.log(x) } } rec(5) //輸出爲5 4 3 2 2 3 4 5
由這個栗子?可知:在遞歸調用語句前的語句執行是正常順序, 可是遞歸調用語句後的執行倒是相反的也就是說在遞歸尚未完成時,函數的輸出結果暫時被掛起,由於通常在計算機中,是以棧的形式來實現遞歸,這個過程以下:算法
5 rec(4) //第一級 5 4 rec(3) //第二級 5 4 3 rec(2) //第三級 5 4 3 2 rec(1) //第四級 console.log() 2 3 4 5 //以出棧形式輸出結果
當遞歸完成時, 執行流開始處理上一級遞歸中的遞歸語句後面的語句, 在這裏是輸出當前變量console.log(x)
編程
遞歸很是適用於相同函數不一樣參數的迭代循環。可是由於須要爲每一級的遞歸開闢內存因此遞歸的開銷相對來講蠻大的, 在不少編程的語言中,對於遞歸的開銷問題有個TCO優化(Tail Call Optimization)戳這篇博客瞭解更多?[翻譯] JS的遞歸與TCO尾調用優化segmentfault
之因此想起碼這篇博客,是由於最近看《算法與數據結構JavaScript描述》(請拉黑此書,bug極多,極不推薦)中使用遞歸遍歷二叉樹的算法挺繞的, 寫篇博客釐清下。
這裏直接借用原書的代碼(有刪改), 以二叉樹的的中序遍歷爲例:數據結構
// 節點對象的構造函數 function Node(data, left, right) { this.data = data; this.left = left; this.right = right; this.show = show; } function show() { return this.data; } //二叉樹的構造函數 function BST() { this.root = null; this.insert = insert; this.inOrder = inOrder; } //插入方法 function insert(data) { var n = new Node(data, null, null); if (this.root == null) { this.root = n; } else { var current = this.root; var parent; while (true) { parent = current; if (data < current.data) { current = current.left; if (current == null) { parent.left = n; break; } } else { current = current.right; if (current == null) { parent.right = n; break; } } } } } //調用兩次遞歸遍歷二叉樹 function inOrder(node) { if (!(node == null)) { inOrder(node.left); console.log(node.show() ) inOrder(node.right); } } //將如下數據導入二叉樹 nums.insert(23) nums.insert(45) nums.insert(16) nums.insert(37) nums.insert(3) nums.insert(99) nums.insert(22) //中序遍歷二叉樹 inOrder(nums.root) /* 輸出結果爲: 3 16 22 23 37 45 99 */
在inOrder函數中使用了兩次遞歸,它的執行順序是:沿左邊找到最小值3,第一次遞歸完成, 以前被掛起的語句開始以出棧的形式執行,輸出無子節點的節點3,而後回到上一級遞歸,輸出其上一級遞歸中的節點16, 在節點16處, 存在子節點,因而執行向右遞歸,執行到無子節點的22,輸出22後返回到節點16 , 執行流繼續往回執行, 執行到根節點23,輸出23後又插入一次向右遞歸,右遞歸到45, 存在左子節點,執行向左遞歸, 以此類推,就完成了這棵二叉樹的中序遍歷函數
附張遍歷順序示意圖:
優化