末尾再帰とは何ですか?アプリケーションシナリオは何ですか?

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;
};

おすすめ

転載: blog.csdn.net/weixin_44761091/article/details/124254174