再帰を使用して問題を解決できるかどうかを判断するための考慮事項は何ですか?


10

インタビューでは、再帰を使用して問題を解決したり(1無限精度の整数に追加するなど)、問題が再帰を使用するのに適している場合があります。時には、問題解決のために再帰を頻繁に使用することが原因である可能性があるため、あまり考えずに、再帰を使用して問題を解決します。

しかし、問題を解決するために再帰を使用することが適切であると判断する前の考慮事項は何ですか?


私が持っていたいくつかの考え:

毎回半分にされるデータに対して再帰を使用すると、再帰を使用しても問題ないように思えます。16GBのRAMまたは8TBのハードドライブに収まるデータはすべて、42レベルの深さの再帰で処理できるためです。(したがって、スタックオーバーフローはありません(一部の環境では、スタックの深さは4000レベル、42を超える可能性がありますが、同時に、各コールスタックがより多くのメモリを占有するため、ローカル変数の数にも依存します多数のローカル変数があり、スタックオーバーフローを決定するのはレベルではなくメモリサイズです)。

純粋な再帰を使用してフィボナッチ数を計算する場合、中間結果をキャッシュしない限り、時間の複雑さを本当に心配する必要があります。

そして1、無限精度の整数に追加するのはどうですか?議論の余地があるかもしれませんが、3000桁または4000桁の数字を扱うので、スタックオーバーフローが発生する可能性がありますか?私はそれを考えていませんでしたが、おそらく答えはノーです、再帰を使用するべきではありませんが、単純なループを使用する必要があります数が素数であるかどうかなど、数のプロパティ。

最終的な質問は、問題を解決するために再帰を使用することを決定する前に考慮すべきことは何ですか?


7
それは実際にはかなり単純です:「少し小さい問題の解決策が知られていると仮定できるなら、解決策は些細なことですか?」
キリアンフォス

しかし、フィボナッチ数または1無限精度の整数に追加するのはどうですか?あなたは言うことができます、はい、彼らはより小さい問題に軽減しますが、純粋な再帰はそれに適していません
非極性

-あなたはこの便利かもしれませんstackoverflow.com/questions/3021/...
Kishor Kundan

回答:


15

考慮すべき点の1つは、アルゴリズムが抽象的なソリューションであるか、実用的な実行可能なソリューションであるかどうかです。前者の場合、探している属性は正確であり、ターゲットオーディエンス1の理解しやすさです。後者の場合、パフォーマンスも問題になります。これらの考慮事項は、選択に影響する場合があります。

2番目の考慮事項(実用的なソリューション)は、使用しているプログラミング言語(より厳密にはその実装)が末尾呼び出しの除去を行うかどうかです。末尾呼び出しを除去しないと、再帰は反復よりも遅くなり、深い再帰はスタックオーバーフローの問題につながる可能性があります。

(正しい)再帰的解法は同等の非再帰的解法に変換できるため、2つのアプローチを厳密に選択する必要は必ずしもないことに注意してください。

最後に、アルゴリズムの特性を(形式的な意味で)証明する必要があるため、再帰的定式化と非再帰的定式化の選択が動機付けられる場合があります。再帰的な定式化により、帰納法による証明がより直接的に可能になります。


1-これに、ターゲットオーディエンスが...などの考慮事項が含まれます。これには、実用的なコードを読むプログラマが含まれるかもしれません...ソリューションの1つのスタイルを他のスタイルよりも「自然」と見なします。「自然」の概念は、プログラミングやアルゴリズムの学習方法に応じて、人によって異なります。(私は、客観的な用語で「自然」を定義するために再帰を使用するかどうかを決定するための主要な基準として「自然」を提案する人に挑戦します。


2
一部の問題は、単純に再帰を使用してより自然に表現されます。たとえば、ツリートラバーサル。
フランクヒルマン

その点に対処するために私の答えを更新しました
スティーブンC

1
「自然さ」に関して:たとえば、再帰のないツリー走査は、より大きく、より汎用性の低いコードを生成する傾向があります。たとえば、葉と複合ノードの動作が異なる、ポリモーフィック呼び出しを使用してツリーを走査することを検討してください。これは再帰なしでは不可能です。
フランクヒルマン

1)「自然」を定義するために私の挑戦をまだ受けましたか?2)スタックデータ構造を使用して再帰をシミュレートできるため、そのようにツリートラバーサルを実装することもできます。それは最も効率的な方法ではないかもしれません...そしてそれはあなたに最も読みやすいコードを提供しません...
スティーブンC

ところで、私が学んだ最初のプログラミング言語(FORTRAN 4)は、再帰をまったくサポートしていませんでした。
スティーブンC

1

C / C ++プログラマーとしての一番の考慮事項はパフォーマンスです。私の決定プロセスは次のようなものです。

  1. 呼び出しスタックの最大の深さは何ですか?深すぎる場合は、再帰を取り除きます。浅い場合は、2に進みます。

  2. この機能は私のプログラムのボトルネックになる可能性がありますか?「はい」の場合、3に進みます。「いいえ」の場合、再帰を保持します。不明な場合は、プロファイラーを実行します。

  3. 再帰的な関数呼び出しに費やされるCPU時間の割合はどれくらいですか?関数呼び出しの時間が他の関数本体よりも大幅に短い場合、再帰を使用しても問題ありません。


0

しかし、問題を解決するために再帰を使用することが適切であると判断する前の考慮事項は何ですか?

Schemeで関数を書くとき、あまり考えずに末尾再帰関数を書くのが自然だと思います。

C ++で関数を作成するとき、再帰関数を使用する前に議論をしています。私が自問する質問は次のとおりです。

  • 反復アルゴリズムを使用して計算を実行できますか?はいの場合、反復アプローチを使用します。

  • 再帰の深さはモデルのサイズによって大きくなりますか?私は最近、モデルのサイズが原因で再帰の深さがほぼ13000に増加した場合に遭遇しました。ヘイスト後の反復アルゴリズムを使用するには、関数を変換する必要がありました。

    このため、再帰関数を使用してツリートラバーサルアルゴリズムを記述することはお勧めしません。実行時の環境に対してツリーが深くなりすぎるのはいつかわかりません。

  • 反復アルゴリズムを使用して、関数が複雑になりすぎることはありますか?はいの場合、再帰関数を使用します。qsort反復的なアプローチを使用して記述しようとはしていませんが、再帰関数を使用する方が自然だと感じています。


0

フィボナッチ数の場合、単純な「再帰」はまったくばかげています。それは同じサブ問題が何度も解決されることにつながるからです。

実際には、再帰が非常に効率的なフィボナッチ数の些細な変動があります:数n≥1が与えられた場合、fib(n)とfib(n-1)の両方を計算します。したがって、2つの結果を返す関数が必要です。この関数fib2を呼び出してください。

実装は非常に簡単です。

function fib2 (n) -> (fibn, fibnm1) {
    if n ≤ 1 { return (1, 1) }
    let (fibn, fibnm1) = fib2 (n-1)
    return (fibn + fibnm1, fibn)
}

共通言語でプログラムを書くことができると思いますか?そして、あなたfib2は数字のペアを返しますが、あなたfib2()はのインターフェースに適合しませんfib()。それは、数字が与えられると、数字を返します。あなたfib(n)は戻ってくるようですが、fib2(n)[0]具体的にしてください
非極性
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.