遞歸基礎
遞歸的概念
- 在程序中函數直接或間接調用自己
- 直接調用自己
- 間接調用自己
- 跳出結構,有了跳出才有結果
遞歸的思想
- 遞歸的調用,最終還是要轉換為自己這個函數
- 如果有個函數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 項開始計算
- 第 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
5. 遞歸函數
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);
}
}
}