彼は本当に神レベルの人物であり、謙虚に崇拝する必要があります
それ自体を呼び出す関数は再帰と呼ばれます。末尾がそれ自体を呼び出す場合、それは末尾再帰と呼ばれます。
再帰は、数千または数百の呼び出しフレームを同時に保存する必要があり、「スタック オーバーフロー」エラーが簡単に発生する可能性があるため、非常にメモリを消費します。ただし、末尾再帰の場合、呼び出しフレームが 1 つだけであるため、「スタック オーバーフロー」エラーは発生しません。
1 2 3 4 5 |
|
上記のコードは階乗関数であり、n の階乗を計算するには、最大 n 件の通話レコードを保存する必要があり、計算量は O(n) です。
末尾再帰として書き換えると、呼び出し記録は 1 つだけ保持され、複雑さは O(1) になります
1 2 3 4 5 |
|
もっと有名な例もあります。これは、フィボナッチ数列を計算するもので、これも完全に説明できます。末尾再帰最適化の重要性。
非末尾再帰フィボナッチ数列は次のように実装されます。
1 2 3 4 5 |
|
最適化後
1 2 3 4 5 |
|
末尾再帰最適化は厳密モードでのみ有効です。つまり、通常モード、またはこの機能をサポートしていない環境でも末尾再帰最適化を使用する方法はありますか?答えは「はい」です。末尾再帰最適化を自分で実装する必要があるだけです。
原理はとてもシンプルです。末尾再帰を最適化する必要がある理由は、呼び出しスタックが多すぎるとオーバーフローが発生するため、呼び出しスタックが削減される限り、オーバーフローは発生しません。コールスタックを減らすにはどうすればよいでしょうか? 「再帰」の代わりに「ループ」を使用してください。
以下は通常の再帰関数です。
1 2 3 4 5 |
|
上記のコードでは、sum は再帰関数、パラメーター x は累積する必要がある値、パラメーター y は再帰の回数を制御します。 sum が 100,000 回再帰するように指定されると、呼び出しスタックの最大数を超えたことを示すエラーが報告されます。
トランポリン機能は、再帰的実行を周期的実行に変換できます。
1 2 3 4 5 |
|
上記は関数 f をパラメータとして受け取るトランポリン関数の実装です。 f が実行後に関数を返す限り、実行は続行されます。ここでは、関数内で関数を呼び出すのではなく、関数を返してからその関数を実行していることに注意してください。これにより、再帰的な実行が回避され、コール スタックが大きすぎる問題が解消されます。
その後、各ステップで別の関数を返すように元の再帰関数を書き直すだけです。
1 2 3 4 5 |
|
上記のコードでは、sum 関数を実行するたびに、それ自体の別のバージョンが返されます。
これで、トランポリン関数を使用して sum を実行するときに、コールスタックのオーバーフローが発生しなくなります。
1 |
|
トランポリン関数は実際の末尾再帰最適化ではありません。以下の実装はです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
上記のコードでは、tco 関数は末尾再帰的最適化の実装であり、その秘密はアクティブ状態変数にあります。デフォルトでは、この変数は非アクティブです。末尾再帰的最適化プロセスに入ると、この変数がアクティブになります。その後、再帰合計の各ラウンドは未定義を返すため、再帰実行は回避され、累積された配列には合計実行の各ラウンドのパラメータが格納され、常に価値があるため、アキュムレータ関数内の while ループが常に実行されることが保証されます。このようにして、「再帰」が「ループ」に巧みに変更され、次のラウンドのパラメーターが前のラウンドのパラメーターを置き換えて、呼び出しスタックの層が 1 つだけになるようにします。
以上がJavaScript における再帰的なスタック オーバーフロー エラーの防止に関する分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。