再帰の実際の例[終了]


94

深さ優先検索(DFS)以外に再帰的アプローチが自然な解決策である実際の問題とは何ですか?

(私はハノイの塔フィボナッチ数、または階乗の実世界の問題を考慮していません。それらは私の心の中で少し工夫されています。)


2
すべての提案に感謝しますが、誰もがツリー/ネットワークトラバーサルを提案しています。これらは基本的にすべての深さ優先検索(または私が推測するBFS)のすべての例です。他のやる気のあるアルゴリズム/問題を探していました。
redfood 2008

10
私はこの質問が好きです!「テクニックXの主な実用的使用を除いて、テクニックXのすべての使用法を教えてください」
ジャスティン・スタンダード

1
私はいつも再帰を使用しますが、通常はマティやグラフィの目的で使用します。私は、非プログラマにとって意味のある再帰の例を探しています。
redfood '09 / 09/23

6
あなた自身の冒険小説を選んでください!私はすべてを読みたいのですが、再帰はそのための最良の方法です。
アンドレス

現実の世界では再帰はありません。再帰は数学的抽象化です。再帰を使用して多くのものをモデル化できます。その意味で、フィボナッチは完全に現実の世界です。この方法でモデル化できる現実世界の問題がかなりあるからです。フィボナッチが実世界ではないと思うなら、私は他のすべての例も抽象化であり、実世界の例ではないと主張します。
ゼーン

回答:


40

ここにはたくさんの具体的な例がありますが、実世界の例が欲しかったので、少し考えてみると、これはおそらく私が提供できる最高のものです。

致命的ではない特定の感染症に感染し、すぐに治癒する人を見つけます(タイプA)。ただし、5人に1人(これらをタイプBと呼びます)を除き、永久に感染し、症状と単に拡散器として機能します。

これにより、タイプBが多数のタイプAに感染すると、非常に煩わしい混乱の波が発生します。

あなたの仕事は、すべてのタイプBを追跡し、免疫して、病気のバックボーンを止めることです。残念ながら、typeAである人々はB型に有効な治療法に対して致命的なアレルギーを持っているため、全国的な治療法をすべてに適用することはできません。

これを行う方法は、社会的発見であり、感染者(タイプA)が与えられた場合、先週のすべての連絡先を選択し、各連絡先をヒープにマークします。感染した人をテストする場合は、「フォローアップ」キューに追加します。人物がタイプBの場合は、頭の「フォローアップ」に追加します(これをすばやく停止したいため)。

特定の人物を処理した後、キューの先頭から人物を選択し、必要に応じて予防接種を適用します。以前に訪問されていない連絡先をすべて取得し、感染していないかどうかをテストします。

感染者のキューが0になるまで繰り返し、次に別の発生を待ちます。

(わかりました、これは少し反復的ですが、再帰的な問題を解決する反復的な方法です。この場合、問題への可能性のあるパスを発見しようとする人口ベースの幅の最初のトラバーサルです。 、そして私はどこでも再帰を強制的に削除しているので、本能的になってしまいます... ...くそっ!


2
ありがとう-これはまだグラフ探索ですが、やる気があり、プログラマーでない人にとっては理にかなっています。
redfood '09 / 09/23

患者0を見つけることが、より良い例になると思います。感染を引き起こした可能性のあるすべての相互作用を特定します。伝染病が発見されなくなるまで、インタラクション時に伝染性であったすべての関係者について繰り返します
ウィリアム・フィッツパトリック

3
この現実世界の例は、今では非常に親しみやすく感じられます:(
haroldolivieri

109

再帰の実例

ひまわり


12
マトリックスアーキテクトによる再帰でコード化された:)
Marcel

3
この再帰はどうですか?かしこまりました。しかし、再帰的ですか?フラクタルキャベツはうまく機能しましたが、この花には自己相似性は見られません。
クレメント

1
まあ、それはほっぺたがちですが、葉序の例です。これは、フィボナッチ数列で説明できます。フィボナッチ数列は、通常、再帰によって実装されます。
Hans Sjunnesson

1
「一般的に再帰を通じて実装される」とは、必ずしも花がそうするという意味ではありません。おそらくそうです。私は分子生物学者を知るのに十分ではありませんが、それがどのように行われるかについての説明がなければ、これは特に役立つとは思いません。反対投票。説明(生物学的に正確であるかどうかに関係なく、説明を追加する場合があります)を付けて説明に加えたい場合は、投票を再考させていただきます。
lindes

65

ファイルシステムのディレクトリ構造に関係するものはどうでしょうか。ファイルの再帰的な検索、ファイルの削除、ディレクトリの作成など。

以下は、ディレクトリとそのサブディレクトリの内容を再帰的に出力するJava実装です。

import java.io.File;

public class DirectoryContentAnalyserOne implements DirectoryContentAnalyser {

    private static StringBuilder indentation = new StringBuilder();

    public static void main (String args [] ){
        // Here you pass the path to the directory to be scanned
        getDirectoryContent("C:\\DirOne\\DirTwo\\AndSoOn");
    }

    private static void getDirectoryContent(String filePath) {

        File currentDirOrFile = new File(filePath);

        if ( !currentDirOrFile.exists() ){
            return;
        }
        else if ( currentDirOrFile.isFile() ){
            System.out.println(indentation + currentDirOrFile.getName());
            return;
        }
        else{
            System.out.println("\n" + indentation + "|_" +currentDirOrFile.getName());
            indentation.append("   ");

            for ( String currentFileOrDirName : currentDirOrFile.list()){
                getPrivateDirectoryContent(currentDirOrFile + "\\" + currentFileOrDirName);
            }

            if (indentation.length() - 3 > 3 ){
                indentation.delete(indentation.length() - 3, indentation.length());
            }
        }       
    }

}

2
ファイルシステムは動機付けを提供します(これは良いことですが、感謝します)。
redfood '09

4
「DFS」という頭字語はわかりませんでした。教室に座ってからしばらく経ちました。
マットディラード

5
深さ優先検索:dfs(node){foreach child in node {visit(child); }}
Haoest

簡単なコード例については、たとえば、stackoverflow.com
questions / 126756 /…を

このコードにエラーはありますか?getPrivateDirectoryContent()をgetDirectoryContent()に置き換えないでください。
Shn_Android_Dev


16

マットディラードの例は良いです。より一般的には、ツリーの任意のウォーキングは、一般的に再帰によって非常に簡単に処理できます。たとえば、解析ツリーのコンパイル、XMLまたはHTMLのウォークスルーなどです。


私はこの「解析木をコンパイルする」ことは賢明な答えだと思います。これには木が含まれますが、質問者が望むように、まだ検索の問題ではありません。これは、言語のコンパイルまたは解釈の一般的な概念に一般化できます。また、英語などの自然言語の「解釈」(理解、聞く)も可能です。
imz-Ivan Zakharyaschev 2011年


13

再帰は、問題を解決するために同じアルゴリズムを使用できるサブ問題に分割することによって問題を解決できる場合はいつでも適切です。ツリーとソートされたリストのアルゴリズムは自然に適合します。計算ジオメトリ(および3Dゲーム)の多くの問題は、バイナリスペースパーティション(BSP)ツリー、ファットサブディビジョン、または世界をサブパートに分割するその他の方法を使用して再帰的に解決できます。

再帰は、アルゴリズムの正確性を保証しようとする場合にも適しています。不変の入力を受け取り、入力に対する再帰的呼び出しと非再帰的呼び出しの組み合わせである結果を返す関数が与えられた場合、通常、数学の帰納法を使用して関数が正しい(または正しくない)ことを証明するのは簡単です。多くの場合、これは、反復関数や、変化する可能性のある入力を使用してこれを行うのは困難です。これは、正確性が非常に重要な財務計算やその他のアプリケーションを処理するときに役立ちます。


11

確かに、多くのコンパイラは再帰を頻繁に使用しています。コンピュータ言語は本質的に再帰的です(つまり、 'if'ステートメントを他の 'if'ステートメント内に埋め込むことができます)。


埋め込まれたifステートメントは再帰ではありません。
John Meagher、

しかし、それらを解析するには、再帰が必要です、ジョン。
Apocalisp 2008

2
John、ifステートメントをネストできるという事実は、言語定義(およびおそらく言語パーサー)が再帰的であることを意味します。
Derek Park、

再帰的降下は、コンパイラーを手動でコーディングする最も簡単な方法の1つです。yaccのようなツールを使用するほど簡単ではありませんが、それがどのように機能するかを理解するのは簡単です。テーブル駆動のステートマシン全体を説明できますが、通常は最終的にブラックボックスになります。
Eclipse

「解析ツリーのコンパイル」について言及しているCody Brociousの回答も、この領域を指摘しています:言語分析/解釈/コンパイル。
imz-Ivan Zakharyaschev 2011年

9

コンテナーコントロール内のすべての子コントロールの読み取り専用を無効/設定します。子コントロールの一部がそれ自体がコンテナだったので、これを行う必要がありました。

public static void SetReadOnly(Control ctrl, bool readOnly)
{
    //set the control read only
    SetControlReadOnly(ctrl, readOnly);

    if (ctrl.Controls != null && ctrl.Controls.Count > 0)
    {
        //recursively loop through all child controls
        foreach (Control c in ctrl.Controls)
            SetReadOnly(c, readOnly);
    }
}

8

SICPからの有名な評価/適用サイクル

代替テキスト
(ソース:mit.edu

evalの定義は次のとおりです。

(define (eval exp env)
  (cond ((self-evaluating? exp) exp)
        ((variable? exp) (lookup-variable-value exp env))
        ((quoted? exp) (text-of-quotation exp))
        ((assignment? exp) (eval-assignment exp env))
        ((definition? exp) (eval-definition exp env))
        ((if? exp) (eval-if exp env))
        ((lambda? exp)
         (make-procedure (lambda-parameters exp)
                         (lambda-body exp)
                         env))
        ((begin? exp) 
         (eval-sequence (begin-actions exp) env))
        ((cond? exp) (eval (cond->if exp) env))
        ((application? exp)
         (apply (eval (operator exp) env)
                (list-of-values (operands exp) env)))
        (else
         (error "Unknown expression type - EVAL" exp))))

適用の定義は次のとおりです。

(define (apply procedure arguments)
  (cond ((primitive-procedure? procedure)
         (apply-primitive-procedure procedure arguments))
        ((compound-procedure? procedure)
         (eval-sequence
           (procedure-body procedure)
           (extend-environment
             (procedure-parameters procedure)
             arguments
             (procedure-environment procedure))))
        (else
         (error
          "Unknown procedure type - APPLY" procedure))))

eval-sequenceの定義は次のとおりです。

(define (eval-sequence exps env)
  (cond ((last-exp? exps) (eval (first-exp exps) env))
        (else (eval (first-exp exps) env)
              (eval-sequence (rest-exps exps) env))))

eval-> apply-> eval-sequence->eval


7

再帰は、ゲーム開発(および他の同様の領域)での衝突検出のためのBSPツリーなどで使用されます。


7

多くの場合、再帰的な方法を使用してドキュメントのスタックを並べ替えます。たとえば、名前の付いた100個のドキュメントを並べ替えているとします。最初に最初の文字で文書を山に配置し、次に各山をソートします。

辞書で単語を検索することは、多くの場合、再帰的なバイナリサーチのような手法で実行されます。

組織では、上司はしばしば部門長にコマンドを与え、次に部長はマネージャーにコマンドを与えます。


5

私が最近得た実際の要件:

要件A:要件Aを完全に理解してから、この機能を実装してください。


4

パーサーとコンパイラーは、再帰降下法で作成できます。lex / yaccのようなツールはより速くより効率的なパーサーを生成するため、これを行うための最良の方法ではありませんが、概念的にはシンプルで実装が容易であるため、一般的です。


4

再帰は問題(状況)に適用され、小さな部分に分割(縮小)できます。各部分は元の問題に似ています。

それ自体と同様の小さなパーツを含むものの良い例は次のとおりです。

  • ツリー構造(ブランチはツリーのようなものです)
  • リスト(リストの一部は引き続きリストです)
  • コンテナ(ロシアの人形)
  • シーケンス(シーケンスの一部は次のようになります)
  • オブジェクトのグループ(サブグループはオブジェクトのグループです)

再帰は、問題の1つが十分に小さくなってケーキになるまで、問題をどんどん小さくしていく手法です。もちろん、それらを分割した後、元の問題の完全なソリューションを形成するために、正しい順序で結果を「ステッチ」する必要があります。

一部の再帰的ソートアルゴリズム、ツリーウォーキングアルゴリズム、マップ/リデュースアルゴリズム、分割統治法はすべて、この手法の例です。

コンピュータプログラミングでは、ほとんどのスタックベースのコールリターン型言語は、再帰のために組み込まれた機能をすでに持っています:すなわち

  • 問題を小さな断片に分解==>元のデータの小さなサブセットでそれ自体を呼び出します)、
  • 断片の分割方法を追跡する==>コールスタック、
  • 結果を元に戻す==>スタックベースのリターン


4

再帰のいくつかの素晴らしい例は、関数型プログラミング言語にあります。関数型プログラミング言語(ErlangHaskellML / OCaml / F#など)では、リスト処理で再帰を使用するのが一般的です。

典型的な命令型OOPスタイルの言語でリストを扱う場合、リンクリストとして実装されたリストを見るのは非常に一般的です([item1-> item2-> item3-> item4])。ただし、一部の関数型プログラミング言語では、リスト自体が再帰的に実装され、リストの「先頭」がリストの最初の項目を指し、「末尾」が残りの項目を含むリストを指すことがわかります( [item1-> [item2-> [item3-> [item4-> []]]]])。それは私の考えではかなり創造的です。

このリストの処理は、パターンマッチングと組み合わせると非常に強力です。数字のリストを合計したいとしましょう:

let rec Sum numbers =
    match numbers with
    | [] -> 0
    | head::tail -> head + Sum tail

これは基本的に「空のリストで呼び出された場合は0を返し(再帰を中断できます)、それ以外の場合はヘッドの値+残りのアイテムで呼び出されたSumの値を返します(したがって、再帰)。

たとえば、URLのリストがある場合、各URLがリンクしているすべてのURLを分解し、すべてのURLとの間のリンクの総数を減らして、ページの「値」を生成します(Googleのアプローチ取りのPageRankとあなたが元に定義されて見つけることができるというのMapReduce)紙。これを行うと、ドキュメント内の単語数も生成できます。そして、多くの、多くの、他の多くのものも同様に。

この機能パターンを任意のタイプのMapReduceコードに拡張して、何かのリストを取得し、それを変換して、別の何かを返すことができます(別のリスト、またはリストのzipコマンド)。


3

XML、またはツリーであるすべてのものをトラバースします。正直なところ、私は仕事で再帰を使用することはほとんどありません。


末尾再帰すらありませんか?
Apocalisp 2008

私は自分のキャリアの中で再帰を使用しましたが、フレームワークが変わったとき、それを取り除きました。私たちの活動の80%はCRUDです。
チャールズグラハム

1
そもそも「XML」について言及するのはかなり奇妙です。それは自然なことではなく、あなたが教えるつもりの通常の人が日常生活の中で対処しなければならないことでもありません。しかし、その考えはもちろんかなり賢明です。
imz-Ivan Zakharyaschev

3

フィードバックは階層構造でループします。

トップボスは、経営幹部に社内の全員からフィードバックを収集するように指示します。

各幹部は自分の直属の部下を集め、直属の部下からフィードバックを収集するように指示します。

そして、ラインダウン。

直属の部下がいない人(ツリーのリーフノード)がフィードバックを提供します。

フィードバックはツリーを上に移動し、各マネージャーが自分のフィードバックを追加します。

最終的には、すべてのフィードバックがトップボスに戻ります。

これは自然な解決策です。これは、再帰的な方法で各レベルでのフィルタリングが可能になるためです。重複の照合と不快なフィードバックの除去。上司グローバルメールを送信し、各従業員にフィードバックを直接報告してもらうことができますが、「真実を処理できない」問題と「解雇された」問題があるため、ここで再帰が最もうまく機能します。


2

WebサイトのCMSを構築していて、ページがツリー構造になっていて、たとえばルートがホームページであるとします。

また、{user | client | customer | boss}が、すべてのページにパンくずリストを配置して、ツリーのどこにいるかを示すように要求したとします。

特定のページnについて、ページツリーのルートに戻るノードのリストを作成するために、nの親、その親などに再帰的に移動することができます。

もちろん、その例ではページごとにdbを数回ヒットしているので、ページテーブルをaとして、ページテーブルをbとしてもう一度検索し、a.idを結合するSQLエイリアスを使用することができます。 b.parentを使用して、データベースに再帰的結合を実行させます。久しぶりなので、私の構文はおそらく役に立たないでしょう。

次に、これを1回だけ計算してページレコードと一緒に保存し、ページを移動した場合にのみ更新することもできます。それはおそらくより効率的です。

とにかく、それは私の$ .02です。


2

Nレベルの深さの組織ツリーがあります。いくつかのノードがチェックされており、チェックされたノードのみに展開したいとします。

これは私が実際にコーディングしたものです。再帰があり、とても簡単です。


2

私の仕事では、ツリーとして記述できる一般的なデータ構造を持つシステムがあります。つまり、再帰はデータを操作するための非常に効果的な手法です。

再帰せずに解決するには、多くの不要なコードが必要になります。再帰の問題は、何が起こるかをたどることが容易でないことです。実行の流れに従うときは、本当に集中する必要があります。しかし、それが機能するとき、コードはエレガントで効果的です。



2
  • XMLファイルの解析。
  • 多次元空間での効率的な検索。例:2Dの四分木、3Dの八分木、kdツリーなど
  • 階層的クラスタリング。
  • 考えてみてください。階層構造をトラバースすることは当然、再帰に役立ちます。
  • ループがなく、再帰が唯一の方法であるC ++のテンプレートメタプログラミング。

「XML」はこの回答のアイデアに不可欠ではありません(そして特にXMLについて言及することは、あなたが教えている人々を不快/退屈にするかもしれません)。典型的な言語(コンピューター言語または自然言語)のすべてが、再帰的な構文解析問題の例になります。
imz-Ivan Zakharyaschev

ポスターは「再帰的アプローチが自然な解決策である実際の問題」を求めました。xmlファイルの解析は確かに現実の問題であり、自然に再帰に役立ちます。XMLに対して奇妙な嫌悪感を抱いているように見えても、XMLが非常に広く使用されているという事実は変わりません。
ディマ


2

私が知っている最も良い例は、クイックソートです。再帰を使用すると、はるかに簡単になります。を見てみましょう:

shop.oreilly.com/product/9780596510046.do

www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047

(第3章の下の最初のサブタイトルをクリックしてください: "私が今まで書いた最も美しいコード")。


1
そして、MergeSortも再帰により簡単です。
Matthew Schinckel 2008

1
リンクが壊れています。本のタイトルを付けてもらえますか?
Peter Mortensen、2014年

@PeterMortensen、本はグレッグウィルソンとアンディオラムによる美しいコードです。リンクを更新しましたが、O'Reillyでは内部を覗くことができなくなったようです。しかし、あなたはアマゾンを見ることができます。
Fabio Ceconello、2014年

1

電話会社とケーブル会社は、実際には大規模なネットワークまたはグラフである配線トポロジのモデルを維持しています。再帰は、すべての親要素またはすべての子要素を検索する場合に、このモデルをトラバースする1つの方法です。

再帰は処理とメモリの観点からコストがかかるため、この手順は通常、トポロジが変更され、結果が変更された事前に並べられたリスト形式で保存された場合にのみ実行されます。


1

概念形成のプロセスである帰納推論は、本質的に再帰的です。現実の世界では、あなたの脳は常にそれを行っています。


1

コンパイラに関するコメントも同様です。抽象構文ツリーのノードは自然に再帰に役立ちます。すべての再帰的なデータ構造(リンクされたリスト、ツリー、グラフなど)も、再帰によってより簡単に処理されます。実際の問題の種類により、学校を卒業するとほとんどの人が再帰をあまり使用できないと思いますが、オプションとして認識しておくのは良いことです。


1

自然数の乗算は、再帰の実際の例です。

To multiply x by y
  if x is 0
    the answer is 0
  if x is 1
    the answer is y
  otherwise
    multiply x - 1 by y, and add x
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.