1.再帰
関数が内部的にそれ自体を呼び出す場合、関数は再帰的です
その核となるアイデアは、大きくて複雑な問題を、解決する元の問題と同様の小さな問題に変換することです。
一般に、再帰には境界条件、再帰的な順方向フェーズ、および再帰的な戻りフェーズが必要です。境界条件が満たされない場合は再帰的に進み、境界条件が満たされた場合は再帰的に戻ります。
factorial(x, n)
以下は階乗を実装する関数を実装します
function factorial(n) {
if(n===1) return n;
return n * factorial(n-1);
}
nが5の場合、このメソッドは最終的な計算式を返す前に5回実行する必要があるため、このメソッドは毎回保存する必要があります。これにより、スタックオーバーフローが発生しやすくなり、複雑さが増します。O(n)
末尾再帰を使用する場合、次のようになります。
2.末尾再帰
末尾再帰、つまり、関数の最後でそれ自体(または末尾がそれ自体を呼び出す関数など)を呼び出します。末尾再帰も再帰の特殊なケースです。末尾再帰は、特殊なタイプの末尾呼び出しです。つまり、末尾で自分自身を直接呼び出す再帰関数です。
再帰呼び出しの過程で、システムは各層のリターンポイントや示強変数などを格納するためのスタックを開きます。再帰時間が長すぎると、スタックオーバーフローが発生しやすくなります。
現時点では、末尾再帰を使用できます。つまり、関数内のすべての再帰呼び出しは関数の最後に表示されます。末尾再帰の場合、呼び出しレコードは1つしかないため、「スタックオーバーフロー」エラーは発生しません。
上記の階乗を実装factorial
するには、次のように末尾再帰を使用します。
function factorial (n, total) {
if(n===1) return total;
return factorial(n-1, n*total);
}
前の関数のパラメータがなければ、新しい関数が返されるたびに、前の関数を保存する必要がないことがわかります。末尾再帰は、呼び出しスタック、複雑さを保存するだけで済みますO(1)
3.末尾再帰のアプリケーションシナリオ
1.配列の合計
function sum (arr, total=1) {
if(arr.length === 1) return total;
return sum(arr, total+arr.pop())
}
2.フィボナッチ数列
function factorial (n, start=1, total=1) {
if(n<=2) return total;
return factorial(n-1, total, total+start)
}
3.アレイの平坦化
function _falt (arr, result=[]) {
arr.forEach(item => {
if(Array.isArray(item)) {
result = result.concat(_falt(item,[]))
} else {
result.push(item)
}
})
return result
}
4.配列オブジェクトのフォーマット
let obj = {
a: '1',
b: {
c: '2',
D: {
E: '3'
}
}
}
// 转化为如下:
let obj = {
a: '1',
b: {
c: '2',
d: {
e: '3'
}
}
}
// 代码实现
function keysLower(obj) {
let reg = new RegExp("([A-Z]+)", "g");
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let temp = obj[key];
if (reg.test(key.toString())) {
// 将修改后的属性名重新赋值给temp,并在对象obj内添加一个转换后的属性
temp = obj[key.replace(reg, function (result) {
return result.toLowerCase()
})] = obj[key];
// 将之前大写的键属性删除
delete obj[key];
}
// 如果属性是对象或者数组,重新执行函数
if (typeof temp === 'object' || Object.prototype.toString.call(temp) === '[object Array]') {
keysLower(temp);
}
}
}
return obj;
};