再帰関数を説明するプログラミング例を提案できますか? Fibonacci seriesやTowersなどの通常の古い馬がありますハノイのですが、それ以外のものは楽しいでしょう。
この図 は、実際のプログラミング言語ではなく英語ですが、プロセスを非技術的な方法で説明するのに役立ちます。
子供は眠れなかったので、母親は小さなカエルの話をしました。 眠れなかったので、カエルの母親は小さなクマの話をしました 眠ることができなかったので、クマの母親は小さなイタチについて話をしました ...眠りに落ちた。 ...そして小さなクマは眠りに落ちました; 。 ..そして小さなカエルは眠りに落ちた; ...そして子供は眠りに落ちた。
再帰の経験則は、「各反復でタスクが2つ以上同様のタスクに分割される場合にのみ再帰を使用する」です。
したがって、フィボナッチは再帰アプリケーションの良い例ではありませんが、ハノイは良い例です。
そのため、再帰の良い例のほとんどは、さまざまなdisquisでのツリー走査です。
例:グラフトラバーサル-訪れたノードが再び訪れないという要件により、グラフは効果的にツリーになります(ツリーは単純なサイクルのない接続されたグラフです)
分割および征服アルゴリズム(クイックソート、マージソート)-「分割」の後の部分は子ノードを構成し、「征服」は親ノードから子ノードにエッジを構成します。
パリンドロームであるために文字列をテストするのはどうですか?
bool isPalindrome(char* s, int len)
{
if(len < 2)
return TRUE;
else
return s[0] == s[len-1] && isPalindrome(&s[1], len-2);
}
もちろん、ループでより効率的に行うことができます。
再帰降下パーサー !
もう1つの「通常の容疑者」は、 Quicksort とMergeSortです。
数学の世界から、 アッカーマン関数 があります。
Ackermann(m, n)
{
if(m==0)
return n+1;
else if(m>0 && n==0)
return Ackermann(m-1, 1);
else if(m>0 && n>0)
return Ackermann(m-1, Ackermann(m, n-1));
else
throw exception; //not defined for negative m or n
}
常に終了しますが、非常に小さな入力であっても非常に大きな結果を生成します。たとえば、Ackermann(4、2)は2を返します65536 − 3。
以下は、ファイルシステムの世界からの実用的な例です。このユーティリティは、指定されたディレクトリの下のファイルを再帰的にカウントします。 (理由は覚えていませんが、実際にはこのようなものが必要でした...)
_public static int countFiles(File f) {
if (f.isFile()){
return 1;
}
// Count children & recurse into subdirs:
int count = 0;
File[] files = f.listFiles();
for (File fileOrDir : files) {
count += countFiles(fileOrDir);
}
return count;
}
_
(in Java a File
インスタンスは通常のファイルまたはディレクトリのいずれかを表すことに注意してください。このユーティリティはディレクトリをカウントから除外します。)
一般的な実世界の例は、たとえば Commons IO ライブラリのFileUtils.deleteDirectory()
API doc & source を参照してください。
私の意見では、再帰は知っておくと良いですが、再帰を使用できるほとんどのソリューションは反復を使用しても実行でき、反復ははるかに効率的です。
これは、ネストされたツリー(ASP.NETやWinformsなど)でコントロールを見つける再帰的な方法です。
public Control FindControl(Control startControl, string id)
{
if (startControl.Id == id)
return startControl
if (startControl.Children.Count > 0)
{
foreach (Control c in startControl.Children)
{
return FindControl(c, id);
}
}
return null;
}
インタープリターのデザインパターン は、多くの人が再帰を見つけられないため、非常に良い例です。ウィキペディアの記事にリストされているサンプルコードは、これをどのように適用できるかをよく示しています。ただし、インタープリターパターンをまだ実装しているはるかに基本的なアプローチは、ネストされたリストのToString
関数です。
class List {
public List(params object[] items) {
foreach (object o in items)
this.Add(o);
}
// Most of the implementation omitted …
public override string ToString() {
var ret = new StringBuilder();
ret.Append("( ");
foreach (object o in this) {
ret.Append(o);
ret.Append(" ");
}
ret.Append(")");
return ret.ToString();
}
}
var lst = new List(1, 2, new List(3, 4), new List(new List(5), 6), 7);
Console.WriteLine(lst);
// yields:
// ( 1 2 ( 3 4 ) ( ( 5 ) 6 ) 7 )
(はい、Eval
という関数を期待している場合、上記のコードでインタープリターパターンを見つけるのは簡単ではないことを知っていますが、実際には、インタープリターパターンは関数が何を呼び出すか、それが何をするかさえ教えてくれませんGoFブックには、上記のパターンの例として上記が明示的にリストされています。)
実際の例は、「部品表原価計算」問題です。
最終製品を製造する製造会社があるとします。各製品は、その部品のリストと、それらの部品を組み立てるのに必要な時間によって説明できます。たとえば、ケース、モーター、チャック、スイッチ、コードから手持ち式の電気ドリルを製造し、5分かかります。
1分あたりの標準的な人件費を考えると、各製品の製造にはどれくらいの費用がかかりますか?
ああ、ところで、一部の部品(コードなど)が購入されているので、コストを直接知っています。
しかし、実際には一部の部品を自社で製造しています。ハウジング、ステーター、ローター、シャフト、ベアリングからモーターを作成します。所要時間は15分です。
そして、スタンピングとワイヤーからステーターとローターを作ります...
したがって、最終製品のコストを決定することは、実際には、プロセス内のすべてのリストからパーツへの関係を表すツリーを走査することになります。それは再帰アルゴリズムでうまく表現されています。確かに反復的に行うこともできますが、コアとなるアイデアは日曜大工の簿記と混同されるため、何が起こっているのかは明確ではありません。
他の人がすでに言ったように、多くの標準的な再帰の例は学術的です。
私が過去に経験したいくつかの実用的な用途は次のとおりです。
1-ファイルシステムやレジストリなどのツリー構造のナビゲート
2-他のコンテナコントロール(パネルやグループボックスなど)を含むコンテナコントロールの操作
私が知っている最も毛深い例は、Knuthの Man or Boy Test です。再帰だけでなく、ネストされた関数定義(クロージャー)、関数参照、および定数/関数の二元性(名前による呼び出し)のALGOL機能を使用します。
私の個人的なお気に入りは Binary Search です
編集:また、ツリートラバーサル。たとえば、フォルダーのファイル構造を調べます。
Implementing Graphs by Guido van Rossumには、グラフ内の2つのノード間のパスを見つけるためのPythonの再帰関数があります。
私のお気に入りのソート、 ソートのマージ
(アルゴリズムを覚えているのでお気に入り そして パフォーマンス的にはそれほど悪くないですか)
昔々、小学生はLogoとTurtle Graphicsを使用して再帰を学びました。 http://en.wikipedia.org/wiki/Turtle_graphics
再帰は、徹底的な試行によるパズルの解決にも最適です。 「塗りつぶし」(Google it)と呼ばれる一種のパズルがあり、そこではクロスワードのようなグリッドと、単語がありますが、手がかりも番号付きの正方形もありません。私はかつてパズル出版社向けに再帰を使ってプログラムを書いて、既知の解決策がユニークであることを確認するためにパズルを解きました。
文字列を逆にするのはどうですか?
void rev(string s) {
if (!s.empty()) {
rev(s[1..s.length]);
}
print(s[0]);
}
これを理解すると、再帰を理解するのに役立ちます。
メニューツリーを再帰的に生成するためにしばらく前にこのサイトに投稿したサンプルを次に示します。 再帰的な例
処理リスト のようなものはどうですか:
再帰関数は、 再帰的に定義されたデータ型 を扱うのに最適です。
等。