遞歸基礎
遞歸的概念
- 在程序中函數直接或間接調用自己
- 直接調用自己
- 間接調用自己
- 跳出結構,有了跳出才有結果
遞歸的思想
- 遞歸的調用,最終還是要轉換為自己這個函數
- 如果有個函數foo,如果他是遞歸函數,到最后問題還是轉換為函數foo的形式
- 遞歸的思想就是將一個未知問題轉換為一個已解決的問題來實現
function foo(){ ...foo(...)... }
遞歸的步驟(技巧)
1. 假設遞歸函數已經寫好 2. 尋找遞推關系 3. 將遞推關系的結構轉換為遞歸體 4. 將臨界條件加入到遞歸體中
簡單遞歸練習
求1-100的和
-
分析:
- 假設遞歸函數已經寫好為sum,既sum(100),就是求1-100的和
- 尋找遞推關系: 就是 n 與 n-1 ,或 n-2 之間的關系
sum(n) == sum(n-1) + n
var res = sum(100); var res = sum(99) + 100;
- 將遞歸結構轉換成遞歸體
function sum(n){ return sum(n-1) + n; }
- 將臨界條件加入到遞歸中
- 求100 轉換為 求99
- 求99 轉換為 求98
- 求98 轉換為 求97
- ...
- 求2 轉換為 求1
- 求1 轉換為 求1
- 即 sum(1) = 1
- 遞歸函數
function sum(n){ if(n==1) return 1; return sum(n-1) + n; }
求 1,3,5,7,9,...第n項的結果和前n項和,序號從0開始
- 分析
- 假設遞歸函數已經完成foo(n),得到奇數
- 遞歸關系:
- foo(n) = foo(n-1)+2
- 遞歸體
function foo(n){ return foo(n) = sum(n-1)+2; }
- 跳出條件
- foo(n) = foo(n-1) + 2
- foo(1) = foo(0) + 2
- foo(0) = 1;
- 遞歸函數
function foo(n){ if(n == 0) return 1; return foo(n-1) + 2; }
* 前 n 項的和 * 分析 1. 假設完成,sum(n)就是前n項的和 2. 遞推關系 * foo(n) = sum(n) + 第n-1項之前的和 3. 遞歸體
function sum(n){ return foo(n) + sum(n-1); }
4. 臨界條件
* n == 1 ,結果為1
5. 遞歸函數
function foo(n){ if(n == 0) return 1; return foo(n-1) + 2; } function sum(n){ if(n == 0) return 1; return foo(n) + sum(n-1); }
求 2,4,6,8,10... 第n項與前n項之和
- 分析
- 假設已知函數 fn(n)為第n項,sum(n)為前n項之和
- 遞歸關系
- fn(n) = fn(n-1) + 2
- sum(n) = fn(n) + sum(n-1)
- 遞歸體
function fn(n){ return fn(n) = (n-1) + 2 } function sum(n){ return sum(n) = fn(n) + sum(n-1); }
- 臨界條件
- fn(0) = 2
- sum(0) = 2;
- 遞歸函數
function fn(n){ if(n == 0) return 2; return fn(n-1) + 2; } function sum(n){ if(n==0) return 2; return fn(n) + sum(n-1); }
數列 1,1,2,4,7,11,16...求第 n 項,求前n項和
- 分析
- 假設已知函數 foo(n) 為第n項
- 遞歸關系
從第 0 項開始計算- 第 0 項, 1 => foo(0) + 0 = foo(1)
- 第 1 項, 2 => foo(1) + 1 = foo(2)
- 第 2 項, 3 => foo(2) + 2 = foo(3)
- ...
- 第 n-1 項, n => foo(n-1) + n-1 = foo(n)
- foo(n) = foo(n-1) + n-1;
- 第 1 項, 2 => fn( 1 ) + 0 = fn( 2 )
- 第 2 項, 3 => fn( 2 ) + 1 = fn( 3 )
- 第 3 項, 4 => fn( 3 ) + 2 = fn( 4 )
- ...
- foo(n) = fn(n-1) + n - 2
- 如果從 0 開始
0 1 2 3 4 5 6
1, 1, 2, 4, 7, 11, 16,
* 如果從 1 開始
1 2 3 4 5 6 7
1, 1, 2, 4, 7, 11, 16
3. 遞歸體
function foo(n){ return foo(n-1)+n-1; }
4. 臨界條件 * foo(0) == 1; * foo(1) == 1; 5. 遞歸函數
function foo(n){ if(n == 0) return 1; return foo(n-1) + n -1; }
* 分析 1. 假設已知函數 sum(n)為前n項和 2. 遞歸關系 * sum(n) = foo(n) + sum(n-1); 3. 遞歸體
function sum(n){ return foo(n) + sum(n-1); }
4. 臨界條件
* sum(0) = 1;
5. 遞歸函數
function sum(n){ if(n == 0) return 1; return foo(n) + sum(n-1); }
Fibonacci數列(斐波那契數列)
1,1,2,3,5,8,13,21,34,55,89...求第 n 項
- 分析
- 假設已知 fib(n) 為第 n 項
- 遞歸關系
- fib(n) = fib(n-1) + fib(n-2)
- 遞歸體
function fib(n){ return fib(n-1)+fib(n-2); }
- 臨界條件
- fib(0) == 1
- fib(1) == 1
- 遞歸函數
function fib(n){ if(n == 0 || n ==1) return 1; return fib(n-1) + fib(n-2); }
高級遞歸練習
階乘
概念:
* 階乘是一個運算, 一個數字的階乘表示的是從 1 開始 累乘到這個數字.
* 例如 3! 表示 1 * 2 * 3
. 5! 就是 1 * 2 * 3 * 4 * 5
. 規定 0 沒有階乘,
* 階乘 從 1 開始.
* 分析:
- 假設已知 foo(n) 為 1-n 的積
- 遞歸關系
* foo(n) = foo(n-1) * n - 遞歸體
function foo(n){ return foo(n-1) * n }
- 臨界條件
* foo(1) == 1 - 遞歸函數
function foo(n){ if( n == 1) return 1; return foo(n - 1) * n; }
求冪
- 概念:
求冪就是求 某一個數 幾次方
2*2 2 的 平方, 2 的 2 次方
求 n 的 m 次方
最終要得到一個函數 power( n, m )
n 的 m 次方就是 m 個 n 相乘 即 n 乘以 (m-1) 個 n 相乘 - 分析
- 假設已知函數 power(n,m) 為 n 的 m 次冪
- 遞歸關系
- power(n,m-1) * n
- 遞歸體
function power(n,m){ return power(n,m-1) * n; }
- 臨界條件
- m == 1 ,return n
- m == 0 ,reutnr 1
- 遞歸函數
function power(n,m){ if(m == 1) return n; return power(n,m-1) * n; }
深拷貝,使用遞歸方式
概念:
- 如果拷貝的時候, 將數據的所有引用結構都拷貝一份, 那么數據在內存中獨立就是深拷貝(內存隔離,完全獨立)
- 如果拷貝的時候, 只針對當前對象的屬性進行拷貝, 而屬性是引用類型這個不考慮, 那么就是淺拷貝
- 拷貝: 復制一份. 指將對象數據復制.
- 在討論深拷與淺拷的時候一定要保證對象的屬性也是引用類型.
實現方法: - 如果要實現深拷貝那么就需要考慮將對象的屬性, 與屬性的屬性,都拷貝過來
- 分析(2個參數,簡單實現)
- 假設已經實現 clone ( o1, o2),將對象 o2 的成員拷貝一份交給 o1
- 遞推關系
- 混合方法,將 o2 的成員拷貝到 o1 中
function clone( o1, o2){ for(var key in o2){ o1[key] = o2[key]; } }
- 假設方法已經實現,如果 o2[key] 是對象
- 繼續使用這個方法
- 需要考慮 o2[key] 是引用類型,再一次使用clone函數
- 如果 o2[key] 不是引用類型,那么直接賦值
- 臨界條件
- 因為是 for in 循環,沒有成員遍歷時,自動結束
- 遞歸函數
function clone(o1,o2){ for(var key in o2){ if(typeof o2[key] == 'object'){ o1[key] = {}; clone(o1[key],o2[key]) }else{ o1[key] = o2[key]; } } }
復雜實現(一個參數)
原理: clone(o) = new Object; 返回一個對象
遞歸函數function clone(o){ var temp = {}; for(var key in o){ if(typeof o[key] == 'object'){ temp[key] = clone(o[key]); }else{ temp[key] = o[key]; } } return temp; }
使用遞歸實現 getElementsByClassName
html結構:
<div> <div>1 <div class="c">2</div> <div>3</div> </div> <div class="c">4</div> <div>5 <div>6</div> <div class="c">7</div> </div> <div>8</div> </div>
分析 1. 實現一個方法byClass()需要的參數是: node: 在某個節點上尋找元素 className: 需要尋找的className arr: 找到的元素存儲到這個數組中 2. 遍歷 node 的子節點, 3. 查看這個子節點是否還有子節點,如果沒有直接存儲到數組中,如果有就繼續遞歸
var arr = []; function byClass(node, className, arr){ //得到傳入節點的所有子節點 var lists = node.childNodes; for(var i = 0;i< lists.length;i++){ //判斷是否有相同className元素 if(arr[i],className == className){ arr.push(arr[i]); } //判斷子節點是否還有子節點 if(arr[i].childNodes.length > 0){ byClass(arr[i],className,arr); } } }