深さ優先検索(DFS)以外に再帰的アプローチが自然な解決策である実際の問題とは何ですか?
(私はハノイの塔、フィボナッチ数、または階乗の実世界の問題を考慮していません。それらは私の心の中で少し工夫されています。)
深さ優先検索(DFS)以外に再帰的アプローチが自然な解決策である実際の問題とは何ですか?
(私はハノイの塔、フィボナッチ数、または階乗の実世界の問題を考慮していません。それらは私の心の中で少し工夫されています。)
回答:
ここにはたくさんの具体的な例がありますが、実世界の例が欲しかったので、少し考えてみると、これはおそらく私が提供できる最高のものです。
致命的ではない特定の感染症に感染し、すぐに治癒する人を見つけます(タイプA)。ただし、5人に1人(これらをタイプBと呼びます)を除き、永久に感染し、症状と単に拡散器として機能します。
これにより、タイプBが多数のタイプAに感染すると、非常に煩わしい混乱の波が発生します。
あなたの仕事は、すべてのタイプBを追跡し、免疫して、病気のバックボーンを止めることです。残念ながら、typeAである人々はB型に有効な治療法に対して致命的なアレルギーを持っているため、全国的な治療法をすべてに適用することはできません。
これを行う方法は、社会的発見であり、感染者(タイプA)が与えられた場合、先週のすべての連絡先を選択し、各連絡先をヒープにマークします。感染した人をテストする場合は、「フォローアップ」キューに追加します。人物がタイプBの場合は、頭の「フォローアップ」に追加します(これをすばやく停止したいため)。
特定の人物を処理した後、キューの先頭から人物を選択し、必要に応じて予防接種を適用します。以前に訪問されていない連絡先をすべて取得し、感染していないかどうかをテストします。
感染者のキューが0になるまで繰り返し、次に別の発生を待ちます。
(わかりました、これは少し反復的ですが、再帰的な問題を解決する反復的な方法です。この場合、問題への可能性のあるパスを発見しようとする人口ベースの幅の最初のトラバーサルです。 、そして私はどこでも再帰を強制的に削除しているので、本能的になってしまいます... ...くそっ!
ファイルシステムのディレクトリ構造に関係するものはどうでしょうか。ファイルの再帰的な検索、ファイルの削除、ディレクトリの作成など。
以下は、ディレクトリとそのサブディレクトリの内容を再帰的に出力する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());
}
}
}
}
マットディラードの例は良いです。より一般的には、ツリーの任意のウォーキングは、一般的に再帰によって非常に簡単に処理できます。たとえば、解析ツリーのコンパイル、XMLまたはHTMLのウォークスルーなどです。
再帰は、問題を解決するために同じアルゴリズムを使用できるサブ問題に分割することによって問題を解決できる場合はいつでも適切です。ツリーとソートされたリストのアルゴリズムは自然に適合します。計算ジオメトリ(および3Dゲーム)の多くの問題は、バイナリスペースパーティション(BSP)ツリー、ファットサブディビジョン、または世界をサブパートに分割するその他の方法を使用して再帰的に解決できます。
再帰は、アルゴリズムの正確性を保証しようとする場合にも適しています。不変の入力を受け取り、入力に対する再帰的呼び出しと非再帰的呼び出しの組み合わせである結果を返す関数が与えられた場合、通常、数学の帰納法を使用して関数が正しい(または正しくない)ことを証明するのは簡単です。多くの場合、これは、反復関数や、変化する可能性のある入力を使用してこれを行うのは困難です。これは、正確性が非常に重要な財務計算やその他のアプリケーションを処理するときに役立ちます。
確かに、多くのコンパイラは再帰を頻繁に使用しています。コンピュータ言語は本質的に再帰的です(つまり、 'if'ステートメントを他の 'if'ステートメント内に埋め込むことができます)。
コンテナーコントロール内のすべての子コントロールの読み取り専用を無効/設定します。子コントロールの一部がそれ自体がコンテナだったので、これを行う必要がありました。
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);
}
}
(ソース: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
再帰は問題(状況)に適用され、小さな部分に分割(縮小)できます。各部分は元の問題に似ています。
それ自体と同様の小さなパーツを含むものの良い例は次のとおりです。
再帰は、問題の1つが十分に小さくなってケーキになるまで、問題をどんどん小さくしていく手法です。もちろん、それらを分割した後、元の問題の完全なソリューションを形成するために、正しい順序で結果を「ステッチ」する必要があります。
一部の再帰的ソートアルゴリズム、ツリーウォーキングアルゴリズム、マップ/リデュースアルゴリズム、分割統治法はすべて、この手法の例です。
コンピュータプログラミングでは、ほとんどのスタックベースのコールリターン型言語は、再帰のために組み込まれた機能をすでに持っています:すなわち
ステートマシンをシミュレートするために、いくつかの場所で純粋な末尾再帰を使用するシステムがあります。
再帰のいくつかの素晴らしい例は、関数型プログラミング言語にあります。関数型プログラミング言語(Erlang、Haskell、ML / 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コマンド)。
XML、またはツリーであるすべてのものをトラバースします。正直なところ、私は仕事で再帰を使用することはほとんどありません。
フィードバックは階層構造でループします。
トップボスは、経営幹部に社内の全員からフィードバックを収集するように指示します。
各幹部は自分の直属の部下を集め、直属の部下からフィードバックを収集するように指示します。
そして、ラインダウン。
直属の部下がいない人(ツリーのリーフノード)がフィードバックを提供します。
フィードバックはツリーを上に移動し、各マネージャーが自分のフィードバックを追加します。
最終的には、すべてのフィードバックがトップボスに戻ります。
これは自然な解決策です。これは、再帰的な方法で各レベルでのフィルタリングが可能になるためです。重複の照合と不快なフィードバックの除去。上司はグローバルメールを送信し、各従業員にフィードバックを直接報告してもらうことができますが、「真実を処理できない」問題と「解雇された」問題があるため、ここで再帰が最もうまく機能します。
WebサイトのCMSを構築していて、ページがツリー構造になっていて、たとえばルートがホームページであるとします。
また、{user | client | customer | boss}が、すべてのページにパンくずリストを配置して、ツリーのどこにいるかを示すように要求したとします。
特定のページnについて、ページツリーのルートに戻るノードのリストを作成するために、nの親、その親などに再帰的に移動することができます。
もちろん、その例ではページごとにdbを数回ヒットしているので、ページテーブルをaとして、ページテーブルをbとしてもう一度検索し、a.idを結合するSQLエイリアスを使用することができます。 b.parentを使用して、データベースに再帰的結合を実行させます。久しぶりなので、私の構文はおそらく役に立たないでしょう。
次に、これを1回だけ計算してページレコードと一緒に保存し、ページを移動した場合にのみ更新することもできます。それはおそらくより効率的です。
とにかく、それは私の$ .02です。
WindowsフォームまたはWebフォーム(.NET Windowsフォーム/ ASP.NET)のコントロールのツリーを解析します。
私が知っている最も良い例は、クイックソートです。再帰を使用すると、はるかに簡単になります。を見てみましょう:
shop.oreilly.com/product/9780596510046.do
www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047
(第3章の下の最初のサブタイトルをクリックしてください: "私が今まで書いた最も美しいコード")。
電話会社とケーブル会社は、実際には大規模なネットワークまたはグラフである配線トポロジのモデルを維持しています。再帰は、すべての親要素またはすべての子要素を検索する場合に、このモデルをトラバースする1つの方法です。
再帰は処理とメモリの観点からコストがかかるため、この手順は通常、トポロジが変更され、結果が変更された事前に並べられたリスト形式で保存された場合にのみ実行されます。