web-dev-qa-db-ja.com

階乗、フィボナッチ数などのない再帰

再帰について見つけることができるほとんどすべての記事には、階乗またはフィボナッチ数の例が含まれています。

  1. 数学
  2. 実生活では役に立たない

再帰を教えるための興味深い数学以外のcodeの例はありますか?

私は分割統治アルゴリズムを考えていますが、それらは通常、複雑なデータ構造を伴います。

48
synapse

ディレクトリ/ファイル構造は、再帰の使用の最良の例です。開始する前に誰もがそれを理解しているためですが、ツリーのような構造を含むものであれば何でも可能です。

void GetAllFilePaths(Directory dir, List<string> paths)
{
    foreach(File file in dir.Files)
    {
        paths.Add(file.Path);
    }

    foreach(Directory subdir in dir.Directories)
    {
        GetAllFilePaths(subdir, paths)
    }
}

List<string> GetAllFilePaths(Directory dir)
{
    List<string> paths = new List<string>();
    GetAllFilePaths(dir, paths);
    return paths;
}
107
pdr

ツリー構造に関係するものを探します。ツリーは比較的理解しやすく、リストなどの線形データ構造よりもはるかに早く再帰的ソリューションの美しさが明らかになります。

考慮すべき事柄:

  • ファイルシステム-それらは基本的にツリーです。素敵なプログラミングタスクは、特定のディレクトリとそのすべてのサブディレクトリの下にあるすべての.jpg画像をフェッチすることです。
  • 祖先-家系図が与えられた場合、共通の祖先を見つけるために歩く必要がある世代の数を見つけます。または、ツリー内の2人が同じ世代に属しているかどうかを確認します。または、ツリー内の2人が合法的に結婚できるかどうかを確認します(管轄によって異なります:)
  • HTMLのようなドキュメント-ドキュメントのシリアル(テキスト)表現とDOMツリーの間の変換。 DOMのサブセットに対して操作を実行します(おそらく、xpathのサブセットを実装することさえできますか?)。 ...

これらはすべて実際の実際のシナリオに関連しており、実際に重要なアプリケーションで使用できます。

51
tdammers

https://stackoverflow.com/questions/105838/real-world-examples-of-recursion

  • 伝染性感染症のモデリング
  • ジオメトリを生成する
  • ディレクトリ管理
  • 仕分け

https://stackoverflow.com/questions/2085834/how-did-you-practically-use-recursion

  • レイトレーシング
  • チェス
  • ソースコードの解析(言語文法)

https://stackoverflow.com/questions/4945128/what-is-a-good-example-of-recursion-other-than-generating-a-fibonacci-sequence

  • BST検索
  • ハノイの塔
  • 回文検索

https://stackoverflow.com/questions/126756/examples-of-recursive-functions

  • 就寝時の物語による再帰を示す素敵な英語の物語を提供します。
41
SF.

ここに私の頭に浮かぶいくつかのより実用的な問題があります:

  • マージソート
  • バイナリ検索
  • ツリーの走査、挿入、および削除(データベースアプリケーションで主に使用されます)
  • 順列ジェネレーター
  • 数独ソルバー(バックトラッキング付き)
  • スペルチェック(ここでもバックトラッキング)
  • 構文解析(例:接頭表記を接尾表記に変換するプログラム)
23
Daniel Scocco

QuickSortは、最初に思い浮かぶものです。バイナリ検索も再帰的な問題です。それ以外にも、再帰の作業を開始すると、解決策がほぼ無料で失敗する問題のクラス全体があります。

11
Zachary K

Pythonで再帰的に定義されたソート。

def sort( a ):
    if len(a) == 1: return a
    part1= sort( a[:len(a)//2] )
    part2= sort( a[len(a)//2:] )
    return merge( part1, part2 )

再帰的に定義されたマージ。

def merge( a, b ):
    if len(b) == 0: return a
    if len(a) == 0: return b
    if a[0] < b[0]:
        return [ a[0] ] + merge(a[1:], b)
    else:
        return [ b[0] ] + merge(a, b[1:]) 

再帰的に定義された線形検索。

def find( element, sequence ):
    if len(sequence) == 0: return False
    if element == sequence[0]: return True
    return find( element, sequence[1:] )

再帰的に定義された二分探索。

def binsearch( element, sequence ):
    if len(sequence) == 0: return False
    mid = len(sequence)//2
    if element < mid: 
        return binsearch( element, sequence[:mid] )
    else:
        return binsearch( element, sequence[mid:] )
8
S.Lott

ある意味で、再帰とはすべてのソリューションを分割して征服することです。つまり、問題のスペースを分割して単純な問題のソリューションを見つけ、通常は元の問題を再構築して正しい答えを作成します。

再帰を教える数学を含まないいくつかの例(少なくとも私が大学時代から覚えている問題):

これらは、問題を解決するためにバックトラッキングを使用する例です。

その他の問題は、人工知能ドメインの古典的なものです:深さ優先検索、パスファインディング、計画の使用。

これらすべての問題には、ある種の「複雑な」データ構造が関係しますが、数学(数値)でそれを教えたくない場合は、選択肢が制限される可能性があります。 Yoyは、リンクされたリストのような基本的なデータ構造で教えることを始めたいと思うかもしれません。たとえば、リストを使用して自然数を表すには:

0 =空のリスト1 = 1つのノードを持つリスト。 2 = 2ノードのリスト。 ...

次に、次のようにこのデータ構造の観点から2つの数値の合計を定義します:空+ N = N Node(X)+ N = Node(X + N)

6
Gabriel

ハノイの塔は、再帰を学ぶのに役立つものです。

Webには、さまざまな言語で多くの解決策があります。

5
Oded

パリンドローム検出器:

文字列で開始: "ABCDEEDCBA"開始文字と終了文字が等しい場合、再帰して "BCDEEDCB"などを確認します。

5
NWS

二分探索アルゴリズムはあなたが望むもののように聞こえます。

関数型プログラミング言語では、高次関数が使用できない場合、変更可能な状態を回避するために、命令ループの代わりに再帰が使用されます。

F#は、両方のスタイルを許可する不純な関数型言語なので、ここで両方を比較します。次のリストのすべての数値を合計します。

可変変数を含む命令ループ

let xlist = [1;2;3;4;5;6;7;8;9;10]
let mutable sum = 0
for x in xlist do
    sum <- sum + x

変更可能な状態のない再帰ループ

let xlist = [1;2;3;4;5;6;7;8;9;10]
let rec loop sum xlist = 
    match xlist with
    | [] -> sum
    | x::xlist -> loop (sum + x) xlist
let sum = loop 0 xlist

この種類の集計isは高次関数List.foldでキャプチャされ、List.fold (+) 0 xlistとして、または便利な関数List.sumだけでList.sum xlistと書くこともできます。

4
Stephen Swensen

私はゲームプレイAIで再帰を多用しました。 C++で作成する場合、お互いを順番に呼び出す一連の約7つの関数を使用しました(最初の関数にはそれらすべてをバイパスし、代わりに2つ以上の関数のチェーンを呼び出すオプションがあります)。どちらかのチェーンの最後の関数は、検索したい残りの深さが0になるまで最初の関数を再度呼び出しました。この場合、最後の関数は評価関数を呼び出し、位置のスコアを返します。

複数の機能により、プレイヤーの決定またはゲーム内のランダムなイベントに基づいて簡単に分岐することができました。非常に大きなデータ構造を渡していたので、可能な限り参照渡しを使用しましたが、ゲームの構造が原因で、検索で「元に戻す」ことは非常に困難でした。一部の関数で値渡しを使用して、元のデータを変更しないようにします。このため、再帰的なアプローチではなくループベースのアプローチに切り替えるのは非常に難しいことが判明しました。

この種のプログラムの非常に基本的な概要を見ることができます https://secure.wikimedia.org/wikipedia/en/wiki/Minimax#Pseudocode を参照してください

3
David Stone

ビジネスにおける実に優れた実例は、「Bill of Materials」と呼ばれるものです。これは、完成品を構成するすべてのコンポーネントを表すデータです。たとえば、自転車を使用してみましょう。自転車には、ハンドルバー、ホイール、フレームなどがあります。これらの各コンポーネントには、サブコンポーネントを含めることができます。たとえば、ホイールにはスポーク、バルブステムなどを含めることができます。したがって、通常、これらはツリー構造で表されます。

ここで、BOMに関する集計情報を照会したり、BOMの要素を変更したりするために、多くの場合、再帰に頼ります。

    class BomPart
    {
        public string PartNumber { get; set; }
        public string Desription { get; set; }
        public int Quantity { get; set; }
        public bool Plastic { get; set; }
        public List<BomPart> Components = new List<BomPart>();
    }

そして、再帰呼び出しのサンプル...

    static int ComponentCount(BomPart part)
    {
        int subCount = 0;
        foreach(BomPart p in part.Components)
            subCount += ComponentCount(p);
        return part.Quantity * Math.Max(1,subCount);

    }

明らかに、BomPartクラスにはさらに多くのフィールドがあります。プラスチックコンポーネントの数、完全なパーツを作成するのにかかる労力などを把握する必要があるかもしれません。これはすべて、ツリー構造での再帰の有用性に戻ります。

3
deepee1