Tail Recursive Function Callにより、コンパイラは通常、定期的な再帰ではできない特別な最適化を実行できます。尾の再帰関数では、再帰コールが実行される最後のものです。この場合、コールごとにスタックフレームを割り当てる代わりに、コンパイラはコードを再加工して現在のスタックフレームを再利用することができます。つまり、テール再帰関数は、数百または数千とは対照的に、単一のスタックフレームのみを使用します。
この最適化は、テールの再帰呼び出しが行われたら、実行するコードがもうないため、変数の以前のコピーが必要になることをコンパイラが知っているため、この最適化が可能です。たとえば、印刷されたステートメントが再帰コールに続いた場合、コンパイラは再帰コールリターン後に印刷する変数の値を知る必要があるため、スタックフレームを再利用できません。
この「スペース節約」とスタックの再利用が実際にどのように機能するかについての詳細情報と、例とともに、次のようになります。 テールコール
編集:これがクイックソートにどのように適用されるかを説明しませんでしたか?まあ、いくつかの用語がその記事に投げ込まれているため、すべてがすべて混乱するようになります(そして、それのいくつかは単なる間違っています)。指定された最初の関数(QuickSort)は、左側に再帰的な呼び出しを行い、右側に再帰的な呼び出しを行い、その後終了します。右側の再帰呼び出しは、関数で起こる最後のことであることに注意してください。コンパイラがTail Recursive Optimizationをサポートする場合(上記で説明)、左呼び出しのみが新しいスタックフレームを作成します。すべての適切な呼び出しは、現在のフレームを再利用するだけです。これは保存できます いくつか スタックフレームがありますが、パーティション化がテールの再帰の最適化が重要ではない一連の呼び出しを作成する場合に依然として苦しむ可能性があります。さらに、右側の呼び出しが同じフレームを使用しても、左側の呼び出しは呼ばれます 内部 右側の呼び出しは、まだスタックを使用しています。最悪の場合、スタックの深さはnです。
説明されている2番目のバージョンはです いいえ 尾の再帰的なクイックソートですが、左並べ替えのみが再帰的に行われ、右のソートがループを使用して行われるクイックソートです。実際、このクイックソート(以前に別のユーザーが説明したように)は、再帰コールが実行する最後のものではないため、テール再帰の最適化を適用することはできません。これはどのように作動しますか?正しく実装すると、QuickSortの最初の呼び出しは、元のアルゴリズムの左側の呼び出しと同じです。ただし、右側の再帰呼び出しも呼ばれません。これはどのように作動しますか?まあ、ループはそれを処理します:「左右」をソートする代わりに、左を呼び出しで並べ替え、右を継続的に並べ替えて並べ替えます。 右の左. 。それは本当にばかげたサウンドですが、基本的には非常に多くの左翼を並べ替えて、権利が単一の要素になり、ソートする必要はありません。これにより、適切な再帰が効果的に削除され、機能が再帰的ではなくなります(擬似再帰的であれば)。ただし、実際の実装では、毎回左側だけを選択しません。最小の側面を選択します。アイデアはまだ同じです。基本的には、両方ではなく片側で再帰的な呼び出しのみを行います。短い側を選ぶと、スタックの深さが適切なバイナリツリーの深さであるLog2(n)よりも大きくなることができないようにします。これは、短い側が常に現在の配列セクションの最大半分のサイズになるためです。ただし、記事で与えられた実装は、これを確実にしません。これは、「左はツリー全体」という同じ最悪のシナリオに苦しむ可能性があるためです。この記事は、実際にもっと読みたいと思っているなら、それについてかなり良い説明をしています。 クイックソートに基づく効率的な選択と部分的な並べ替え