再帰について見つけることができるほぼすべての記事には、階乗またはフィボナッチ数の例が含まれています。
- 数学
- 実生活では役に立たない
再帰を教えるための興味深い数学以外のコード例はありますか?
私は分割統治アルゴリズムを考えていますが、通常は複雑なデータ構造を伴います。
再帰について見つけることができるほぼすべての記事には、階乗またはフィボナッチ数の例が含まれています。
再帰を教えるための興味深い数学以外のコード例はありますか?
私は分割統治アルゴリズムを考えていますが、通常は複雑なデータ構造を伴います。
回答:
ディレクトリ/ファイル構造は再帰を使用する最良の例です。なぜなら、開始する前に誰もがそれらを理解しているからです。しかし、ツリーのような構造を含むものなら何でもできます。
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;
}
ツリー構造に関係するものを探します。ツリーは比較的把握しやすく、再帰的なソリューションの美しさは、リストなどの線形データ構造よりもはるかに早く明らかになります。
考えるべきこと:
これらはすべて実際の現実世界のシナリオに関連しており、現実世界で重要なアプリケーションですべて使用できます。
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/126756/examples-of-recursive-functions
QuickSortは最初に思い浮かぶものです。バイナリ検索も再帰的な問題です。それ以外にも、再帰を使って作業を開始すると、ソリューションがほぼ無料で抜け落ちてしまう問題のクラスがあります。
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:] )
ある意味で、再帰とは解決策を分割して征服することです。つまり、問題空間をより小さなものに分割して単純な問題の解決策を見つけ、通常は元の問題を再構築して正しい答えを構成します。
再帰を教えるために数学を使わないいくつかの例(少なくとも大学時代から覚えている問題):
これらは、バックトラッキングを使用して問題を解決する例です。
他の問題は、人工知能ドメインの古典的なものです:深さ優先検索の使用、経路探索、計画。
これらの問題はすべて、ある種の「複雑な」データ構造に関係していますが、数学(数字)で教えたくない場合は、選択肢がより制限される可能性があります。Yoyは、リンクリストのような基本的なデータ構造でティーチングを開始する場合があります。たとえば、リストを使用して自然数を表す:
0 =空のリスト1 = 1つのノードを持つリスト。2 = 2ノードのリスト。...
次のように、このデータ構造に関して2つの数値の合計を定義します。空+ N = N Node(X)+ N = Node(X + N)
バイナリ検索アルゴリズムは、あなたが望むもののように聞こえます。
関数型プログラミング言語では、高階関数が利用できない場合、可変状態を回避するために命令型ループの代わりに再帰が使用されます。
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
この種の集約は高次関数でキャプチャされ、単にとして便利な関数として、または実際にはさらに簡単にList.fold
記述できることに注意してください。List.fold (+) 0 xlist
List.sum
List.sum xlist
AIをプレイするゲームで再帰を多用しました。C ++で記述し、互いに順番に呼び出す一連の約7つの関数を使用しました(最初の関数には、それらすべてをバイパスし、代わりに2つの関数のチェーンを呼び出すオプションがあります)。いずれかのチェーンの最終関数は、検索したい残りの深さが0になるまで最初の関数を再度呼び出しました。その場合、最終関数は評価関数を呼び出して位置のスコアを返します。
複数の機能により、プレイヤーの決定またはゲーム内のランダムなイベントに基づいて簡単に分岐できました。非常に大きなデータ構造をやり取りしていたため、できる限り参照渡しを使用していましたが、ゲームの構造が原因で、検索で「移動を取り消す」ことは非常に困難でした。元のデータを変更しないために、一部の関数で値渡しを使用します。このため、再帰的なアプローチではなくループベースのアプローチに切り替えるのは非常に困難であることが判明しました。
この種のプログラムの非常に基本的な概要を見ることができます。https://secure.wikimedia.org/wikipedia/en/wiki/Minimax#Pseudocodeを参照してください
ビジネスでの実に良い実例は、「部品表」と呼ばれるものです。これは、完成品を構成するすべてのコンポーネントを表すデータです。たとえば、自転車を使用しましょう。自転車には、ハンドルバー、ホイール、フレームなどがあります。また、これらの各コンポーネントにはサブコンポーネントを含めることができます。たとえば、ホイールはスポーク、バルブステムなどを持つことができます。したがって、通常、これらはツリー構造で表されます。
ここで、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クラスにはさらに多くのフィールドがあります。あなたが持っているプラスチック部品の数、完全な部品を作るのにどれだけの労力がかかるかなどを理解する必要があるかもしれません。これはすべて、ツリー構造での再帰の有用性に戻っています。
家族関係は良い例になります。誰もが直感的に理解しているからです。
ancestor(joe, me) = (joe == me)
OR ancestor(joe, me.father)
OR ancestor(joe, me.mother);
||
は、などの適切な演算子を置き換える必要がありますOR
。
再帰プログラムを使用して、チェス盤上のナイトパターンに関する問題を解決しました。騎士を動かして、いくつかのマークされた正方形を除くすべての正方形に触れるようにする必要がありました。
あなたは単に:
mark your "Current" square
gather a list of free squares that are valid moves
are there no valid moves?
are all squares marked?
you win!
for each free square
recurse!
clear the mark on your current square.
return.
このようなツリーで将来の可能性をテストすることで、多くの種類の「先読み」シナリオを機能させることができます。