遞歸
什么是遞歸
在程序中, 所謂的遞歸, 就是函數自己直接或間接的調用自己.
- 直接調用自己
- 間接調用自己
就遞歸而言最重要的就是跳出結構. 因為跳出了才可以有結果.
所謂的遞歸就是化歸思想
遞歸的調用, 寫遞歸函數, 最終還是要轉換為自己這個函數.
假如有一個函數 f, 如果它是遞歸函數的話, 那么也就是說 函數體內的問題還是轉換為 f 的形式.
遞歸思想就是將一個問題轉換為一個已解決的問題來實現
function f() {
... f( ... ) ...
}
例子: 1, 2, 3, 4, 5, ..., 100
- 首先假定遞歸函數已經寫好, 假設是 foo. 即 foo( 100 ) 就是求 1 到 100 的和
- 尋找遞推關系. 就是 n 與 n-1, 或 n-2 之間的關系: foo( n ) == n + foo( n - 1 )
var res = foo( 100 );
var res = foo( 99 ) + 100;
- 將遞推結構轉換為 遞歸體
function foo( n ) {
return n + foo( n - 1 );
}
* 將 求 100 轉換為 求 99
* 將 求 99 轉換為 求 98
* ...
* 將求 2 轉換為 求 1
* 求 1 結果就是 1
* 即: foo( 1 ) 是 1
- 將臨界條件加到遞歸體中
function foo( n ) {
if ( n == 1 ) return 1;
return n + foo( n - 1 );
}
練習: 求 1, 3, 5, 7, 9, ... 第 n 項的結果與前 n 項和. 序號從 0 開始
求第 n 項的
- 首先假定遞歸函數已經寫好, 假設是 fn. 那么 第 n 項就是 fn( n )
- 找遞推關系: fn( n ) == f( n - 1 ) + 2
- 遞歸體
function fn( n ) {
return fn( n-1 ) + 2;
}
- 找臨界條件
- 求 n -> n-1
- 求 n-1 -> n-2
- ...
- 求 1 -> 0
- 求 第 0 項, 就是 1
- 加入臨界條件
function fn( n ) {
if ( n == 0 ) return 1;
return fn( n-1 ) + 2;
}
前n項和
- 假設已完成, sum( n ) 就是前 n 項和
- 找遞推關系: 前 n 項和 等於 第 n 項 + 前 n-1 項的和
- 得到遞歸體
function sum( n ) {
return fn( n ) + sum( n - 1 );
}
- 找臨界條件
- n == 1 結果為 1
- 得到遞歸函數
function sum( n ) {
if ( n == 0 ) return 1;
return fn( n ) + sum( n - 1 );
}
練習: 2, 4, 6, 8, 10 第 n 項與 前 n 項和
第n項
function fn( n ) {
if ( n == 0 ) return 2;
return fn( n-1 ) + 2;
}
前n項和
function sum( n ) {
if ( n == 0 ) return 2;
return sum( n - 1 ) + fn( n );
}
練習: 數列: 1, 1, 2, 4, 7, 11, 16, … 求 第 n 項, 求前 n 項和.
求第 n 項
- 假設已經得到結果 fn, fn( 10 ) 就是第 10 項
- 找遞推關系
- 0, 1 => fn( 0 ) + 0 = fn( 1 )
- 1, 2 => fn( 1 ) + 1 = fn( 2 )
- 2, 3 => fn( 2 ) + 2 = fn( 3 )
- ...
- n-1, n => fn( n-1 ) + n - 1 = fn( n )
- 遞歸體也就清楚了, 臨界條件是 n == 0 => 1
function fn( n ) {
if ( n == 0 ) return 1;
return fn( n-1 ) + n - 1;
}
如果從 1 開始表示, 那么第 n 項為
- 假設已經得到結果 fn, fn( 10 ) 就是第 10 項
- 找遞推關系
- 1, 2 => fn( 1 ) + 0 = fn( 2 )
- 2, 3 => fn( 2 ) + 1 = fn( 3 )
- 3, 4 => fn( 3 ) + 2 = fn( 4 )
- ...
- n-1, n => fn( n-1 ) + n - 2 = fn( n )
- 臨界條件 n == 1 => 1
前n項和
function sum( n ) {
if ( n == 0 ) return 1;
return sum( n - 1 ) + fn( n );
}
如果從 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,
練習: Fibonacci 數列: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …
求其第 n 項.
遞推關系 fn(n) == fn( n- 1) + fn( n - 2)
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 開始.
求 n 的階乘
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 相乘
function power ( n, m ) {
if ( m == 1 ) return n;
return power( n, m - 1 ) * n;
}
深拷貝
如果要實現深拷貝那么就需要考慮將對象的屬性, 與屬性的屬性, ... 都拷貝過來
如果要實現:
- 假設已經實現 clone( o1, o2 ), 將對象 o2 的成員拷貝一份交給 o1
- 簡單的算法, 將 o2 的屬性拷貝到 o1 中去
function clone( o1, o2 ) {
for ( var k in o2 ) {
o1[ k ] = o2[ k ];
}
}
- 找遞推關系, 或叫划歸為已經解決的問題
- 假設方法已經實現, 問一下, 如果 o2[ k ] 是對象
- 繼續使用這個方法
- 因此需要考慮的是 o2[ k ] 如果是引用類型, 再使用一次 clone() 函數
- 如果 o2[ k ]不是引用類型, 那么 就直接賦值
function clone( o1, o2 ) {
for ( var k in o2 ) {
if ( typeof o2[ k ] == 'object' ) {
o1[ k ] = {};
clone( o1[ k ] , o2[ k ] );
} else {
o1[ k ] = o2[ k ];
}
}
}
復雜實現: clone( o ) -> newObj
function clone( o ) {
var temp = {};
for ( var k in o ) {
if ( typeof o[ k ] == 'object' ) {
temp[ k ] = clone( o[ k ] );
} else {
temp[ k ] = o[ k ];
}
}
return temp;
}
請用 遞歸實現 getElementsByClassName
<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>
- 如果實現一個方法 byClass( node, 'c', list ), 表示在某一個節點上查找符合 class 屬性為 c 的元素
- 在當前元素的子元素中查找, 如果有符合要求的, 存儲到一個數組中
- 首先遍歷 子節點, 然后看子節點是否還有子節點, 如果沒有直接判斷, 如果有再遞歸
function byClass( node, className, list ) {
var arr = node.childNodes;
for ( var i = 0; i < arr.length; i++ ) {
if ( arr[ i ].className == className ) {
list.push( arr[ i ] );
}
if ( arr[ i ].childNodes.length > 0 ) {
byClass( arr[ i ], className, list );
}
}
}