再帰の理解を改善するためのリソースはありますか?[閉まっている]


13

私は再帰とは何かを知っています(通常、パテンが自身内で発生するとき、通常はブレークアウト条件付き...の後にその行の1つでそれ自体を呼び出す関数ですか?)、それらを詳しく調べれば再帰関数を理解できます。私の問題は、新しい例を見ると、最初はいつも混乱していることです。ループ、またはマッピング、ジッピング、ネスト、ポリモーフィックコールなどが表示される場合、それを見るだけで何が起こっているかがわかります。再帰的なコードを見ると、私の思考プロセスは通常「wtf is this?」です。続けて「ああ、再帰的」、続いて「もし彼らが言うなら、うまくいくに違いない」と続きます。

この分野でスキルを構築するためのヒント/計画/リソースはありますか?再帰は奇妙な概念の一種なので、それに取り組む方法も同様に奇妙で明白ではないかと考えています。


28
再帰を理解するには、まず再帰を理解する必要があります。
アンドレアスヨハンソン

1
スース博士による「帽子の猫が帰ってきた」、これは完全に有用ではないかもしれませんが、猫の再帰呼び出しはその厄介な汚れを取り除きます。:-)また、非常に迅速に読めるという利点もあります!
DKnight

2
練習、練習、練習。
デビッドソーンリー


3
@Graham Borland:それは無限再帰の例です。ほとんどのプログラムでは、基本ケースがないと、通常、スタックオーバーフローまたはメモリ不足エラーが発生します。Webサイトのユーザーにとっては、混乱を招く可能性があります。;)
FrustratedWithFormsDesigner

回答:


10

シンプルなものから始めて、鉛筆と紙でそれをトレースします。うんざり。開始するのに適した場所は、ツリートラバーサルアルゴリズムです。これは、通常の反復よりも再帰を使用するとはるかに簡単に処理できるためです。複雑な例である必要はありませんが、シンプルで作業できるものです。

はい、奇妙で直観に反することもありますが、一度クリックすると、「ユーリカ!」と言うと あなたはそれを前にどのように理解しなかったのか不思議に思うでしょう!;)再帰を理解するのに(IMO)最も簡単な構造であり、鉛筆と紙を使用して簡単に操作できるため、木を提案しました。;)


1
+1これは私がそれを盗んだ方法です。たとえば、OOを使用している場合、親子関係を持つクラスを作成してから、オブジェクトに特定の祖先があるかどうかをチェックする関数/メソッドを作成してみてください。
-Alb

5

本「Little Lisper」を使用して、Schemeを強くお勧めします。あなたがそれを完了したら、あなたはなり再帰、深いダウンを理解しています。ほぼ保証されています。


1
+1この本は本当に私のためにやりました。しかし、「リトル
スキーム

4

SICPをお勧めします。また、ここで著者の入門コースビデオをチェックアウトする必要があります。彼らは信じられないほど心を開いています。

プログラミングにそれほど厳密に関連していない別の道は、バッハ、エッシャー、ゲーデルを読んでいます。ホフスタッターによる永遠の黄金の編組です。一度トラフを取得すると、再帰は算術と同じように自然に見えます。また、P = nPであると確信し、思考マシンを構築する必要がありますが、それは利点と比較すると非常に小さな副作用です。


とにかくGEBは読む価値があります。彼が話していることの一部が少し時代遅れであったとしても(過去40年間にCSの基礎研究の進展があった)、基本的な理解はそうではありません。
ドナルドフェローズ

2

基本的には実践に帰着するだけです...一般的な問題(ソート、検索、数学の問題など)を取り上げ、1つの関数を複数回適用した場合にそれらの問題を解決できる方法を確認できるかどうかを確認します。

たとえば、クイックソートは、リスト内の要素を2つの半分に移動してから、それらの各半分に再び適用するという点で動作します。最初の並べ替えが発生しても、その時点で2つの半分を並べ替えることを心配していません。ピボット要素を取得し、その要素よりも小さいすべての要素を一方の側に配置し、それ以上のすべての要素を反対側に配置します。これは、その時点でそれ自体を再帰的に呼び出して、2つの新しい小さなリストをソートする方法を理解できますか?それらもリストです。ただ小さい。しかし、それらはまだソートする必要があります。

再帰の背後にある力は、分割と征服の概念です。問題を、本質的には同一であるがより小さい、より小さな問題に繰り返し分割します。最終的にそれを十分に行うと、残りの唯一のピースがすでに解決されているポイントに到達した場合、ループから抜け出すだけで問題が解決します。 あなたそれらを理解するまで、あなたが言及したそれらの例研究してください。しばらく時間がかかりますが、最終的には簡単になります。次に、他の問題を取り、それらを解決するための再帰関数を作成してみてください!幸運を!

編集:また、再帰の重要な要素は、関数が停止できることが保証されていることです。これは、元の問題の内訳が継続的に小さくなり、最終的に保証された停止ポイント(新しいサブ問題が解決可能または既に解決されているポイント)が必要になることを意味します。


ええ、前に簡単な説明を見たことがあると思います。上記のリマインダーからそれがどのように機能するか想像できます。再帰はどのように表現的/柔軟性に富んでいますか-ほとんどの問題は再帰的アプローチに強制できますか(最適ではない場合でも)。ほとんどの人が手続き的に取り組んでいるネット上のコーディングパズルに答えている人を見たことがあります。まるで必要なときにいつでも再帰を使用できるかのようです。私はまた、いくつかの言語がループ構造を置き換えるために依存または再帰していることを一度読みました。そして、あなたは保証された停止点に言及します。これらのことの1つが鍵になると思います。
アンドリューM

自分で作成するための良い簡単な開始問題は、数値の階乗を見つける再帰プログラムを書くことです。
ケネス

ループ構造は、再帰構造に入れることができます。再帰構造は、ループ構造に入れることができます...多かれ少なかれ。再帰を使用する場合、ハードウェアレベルで使用されるリソースに関してオーバーヘッドが非常に多いことを覚えておく必要があるため、再帰を使用する場合としない場合を学習できるようになるには、時間と練習が必要です。
ケネス

たとえば、クイックソートを実行するループ構造を作成することは実現可能であることがわかりました。大きなアレイ用。
ケネス

だから、階乗での私の試みです。公平を期すために、私は以前にこれを見てきました。メモリではなくゼロから作成しましたが、おそらく以前よりも簡単です。JSで試してみましたが、解析エラーがありましたが、Pythonで動作します def factorial(number): """return factorial of number""" if number == 0: return 0 elif number == 1: return 1 else: return number * factorial(number - 1)
アンドリューM

2

個人的に私はあなたの最善の策は練習を通してだと思います。

ロゴで再帰を学びました。LISPを使用できます。再帰はこれらの言語では自然です。それ以外の場合は、数学的スイートとシリーズの研究に例えることができます。このシリーズでは、u(n + 1)= f(u(n))、または複数の変数があり、複数の依存関係、たとえばu(n)= g(u(n-1)、u(n-2)、v(n)、v(n-1)); v(n)= h(u(n-1)、u(n-2)、v(n)、v(n-1))...

したがって、私の提案は、単純な(それらの表現で)標準的な再帰「問題」を見つけて、選択した言語でそれらを実装することです。練習は、それらの「問題」を考え、読み、表現する方法を学ぶのに役立ちます。多くの場合、これらの問題のいくつかは反復によって表現できますが、再帰はそれらを解決するよりエレガントな方法である可能性があることに注意してください。それらの1つは、階乗数の計算です。

グラフィカルな「問題」により、見やすくなります。コッホのフレーク、フィボナッチ、ドラゴンカーブ、フラクタル全般を調べてください。しかし、クイックソートアルゴリズムも見てください...

いくつかのプログラムをクラッシュさせ(無限ループ、無限のリソースの暫定的な使用)、終了条件を誤って処理して(予期しない結果を得る)必要があります。そして、あなたがそれを手に入れたとしても、あなたはそれでもこれらの間違いを犯すでしょう。



0

以下のように私と同じくらいSICP永遠のゴールデン組紐:ゲーデル、エッシャー、バッハ、TouretzkyのLISP:記号計算にAジェントル入門も再帰を導入するには良い仕事をしていません。

基本的な概念は次のとおりです。まず、結果を返すことができるように、再帰関数がいつ終了するかを知る必要があります。次に、未完成のケースをどのように取り、再発できるものに減らすかを知る必要があります。従来のfactorial(N)の例では、N <= 1で終了し、未完成のケースはN * factorial(N-1)です。

よりmuchい例として、アッカーマンの関数 A(m、n)があります。

A(0,n) = n+1.                                   This is the terminal case.
A(m,0) = A(m-1,1) if m > 0.                     This is a simple recursion.
A(m,n) = A(m-1, A(m, n-1)) if m > 0 and n > 0.  This one is ugly.

0

OCamlやHaskellなどのMLスタイルの関数型言語で遊んでみることをお勧めします。パターンマッチング構文、Scheme ifcondステートメントよりもはるかに優れた、比較的複雑な再帰関数を理解するのに本当に役立つことがわかりました。(HaskellとSchemeを同時に学びました。)

対照的な簡単な例を次に示します。

(define (fib n)
   (cond [(= n 0) 0]
         [(= n 1) 1]
         [else (+ (fib (- n 1)) (fib (- n 2)))]))

パターンマッチングを使用した場合:

fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

この例は実際には違いを正しません。どちらのバージョンの関数にも問題はありませんでした。2つのオプションがどのように見えるかを説明するだけです。リストやツリーなどを使用して、はるかに複雑な関数に到達すると、その違いはさらに顕著になります。

Haskellは、非常に優れた構文を持つ単純な言語であるため、特にお勧めします。また、corecursionのようなより高度なアイデアを扱うのがはるかに簡単になります

fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)
fib n = fibs !! n

(上記のコードはHaskellで少し遊ぶまで理解できませんが、基本的には魔法のようです:P。)Schemeのストリームでも同じことができますが、Haskellの方がはるかに自然です。


0

それは絶版ですが、それを見つけることができれば、リチャード・ローレンツによる「再帰アルゴリズム」は再帰に他なりません。再帰の基本と、特定の再帰アルゴリズムについて説明しています。

例はPascalにありますが、それほど大きくないため、言語の選択は面倒です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.