再帰の問題について:
再帰関数を作成すると、呼び出しスタックが作成されます。わかりました。しかし、 このページ (「LKM」によるコメントを探す)へのコメントは、好奇心を刺激しました(Googleは役に立ちませんでした)。
補助的な質問(別の質問に値するかもしれませんが、どの(。* \。)?stack。* \。comに質問するかわかりません):
プログラミングに関するこれらの会話では、「再帰」テーマと、悪いプログラマーや初心者プログラマーがそれを巧みに処理しない方法をよく目にします。私は独学で、大騒ぎが何であるかを理解できませんでした。私は日常のコーディングで再帰を頻繁に使用しています。問題を解決するためだけでなく、美しいと思うからといって、再帰を使用することもあります。しかし、これらの記事は、たぶん私が見ないものがあるかもしれないような気がします。そう:
「ローカル」スタックは、そのコメントで使用されているように、暗黙的にローカル変数として宣言されたスタックを意味します。 「明示的」スタックと呼んで、コールスタックと区別します。
明示的なスタックでの反復は、末尾再帰などの再帰関連の最適化をサポートしていない言語での再帰よりも高速です。または、言語のスタック深度が制限されている場合があります。
これが、エリック・リッパートによる 例を用いた良い説明 です。
LKMは、手動で処理されたスタックを意味します。たとえば、先頭に要素をプッシュしてから再びポップするリンクリストなどです。小さく、シャッフルするデータが少ないため、高速になります。
これは、次の要素へのポインタ(または必要に応じて参照)である構造体のフィールドと、スタックの最上位であるヘッド変数によって実装されます。プッシュするときは、プッシュされた要素の次のポインターをヘッド変数の現在の値に設定し、プッシュされた要素を参照するようにヘッド変数を設定します。ポップはその逆です。
スタックの最大スタックがわかっている場合、それを実装する別の方法は、通常の配列と、配列のどこにヘッドがあるかを示すインデックス変数を用意することです。単純化すれば、それが実際に再帰呼び出しスタックが実装される方法です。
上記のほとんどの言語には、配列をスタックとして扱うための特別な構成要素もあると思います。これは通常、プッシュ/ポップまたはシフト/アンシフトと呼ばれます。
この場合、ローカルスタックを使用することは、「ローカル変数、たとえばスタックのようにアクセスされる配列を使用すること」、つまりFIFOを意味し、「それぞれがintを含むスタックフレームを作成する関数呼び出しを行うそのうちの1つに、配列と同じ値が含まれています。」レジスターのプッシュとポップのオーバーヘッドを回避し、各関数の呼び出しごとにローカルスタックフレームを設定するなどの理由で、より高速になる可能性があります。代わりに、メモリ割り当てを1つだけ実行し、それだけです(alloca
次に、ローカルスタックは実際にis呼び出しスタックにあります-alloca
は本質的に単なるdexインクリメントですが、malloc
は、あらゆる種類の高価な操作を伴う場合があります)。
いつものように、トレードオフは、スタックの機構を可視化することにより、アルゴリズムの実際の再帰的な構造が多くの人々にとってはるかに不明確になることです。私は「多くの人」と言っていることに注意してください-再帰を自然に理解する人もいれば、再帰を理解しない人もいます。 (私は昔、SchemeとPrologを新入生に教えていましたが、大体3分の1はし、3分の2はしません。)全体 books は、後者の種類を取得するためにのみ記述されています。このハードルを乗り越えて学生の皆さん、そしてあなたは前者に属しているようですので、コンセプトが多くの人にどれほど神秘的であるかをあなたに伝えることができるかどうかわかりません。発生する一連のステップを理解することさえも問題ではなく、信じるの問題は、まだ完成していない解決策には、多くの練習と、いくつかの小さなおよび中規模の例のステップ実行が含まれます。最終的には、これは多くの学習者にとってペニーが下がることになりますが、すべてではありません。