記事ディレクトリ
再帰関数とは何ですか?
再帰関数の標準的な定義は、関数fを定義することです。fは直接または間接的に自分自身を呼び出し、f関数は再帰関数と呼ばれます。
さらに苦労することなく、サンプルコードは次のとおりです。
// 打印从10到0
public class 递归函数示例 {
// 递归函数
static void f(int m){
// 出口,
// 如果这段代码取消,你的程序一定会报栈越界的错误:java.lang.StackOverflowError
if(m < 0)
return;
System.out.println(m);
f(m-1); // 我调用我自己!
}
public static void main(String[] args) {
f(5);
}
}
これで、関数f()がそれ自体(人形)を呼び出すことがわかります。
それでは、このコードの呼び出しロジックを詳しく見てみましょう。
コンソールの結果は次のようになります。
再帰関数を使用して問題を解決する方法は?
Ⅰ。定義した再帰関数の意味を覚えておいてください
再帰関数とそのパラメータに含まれる意味は非常に重要です。意味と意味を定義します。問題を解決するプロセスでは、関数の意味とそのパラメータの意味を常に覚えておく必要があります。
たとえば、nの階乗を見つける問題では、f(n)をnの階乗として定義し、f(n-1)はn-1の階乗でなければならず、f(n-2)はn-の階乗です2の階乗。
Ⅱ。再帰関数の3つの「発見」
再帰関数を設計するには、一般に3つのことを見つける必要があります。
- サブ問題(再帰関数のサブ問題)を見つける
- 除算時の変化量を調べる(再帰関数のパラメーター)
- 口を見つける(再帰関数の終わり)
次に、これら3つの「検索」について詳しく説明します。(ps:突然、言葉がわからないことがわかりました)
1.副問題(再帰関数の副問題)を見つける
いわゆるサブ問題は、元の問題を解体し、元の問題と同じくらい論理的な小さなサブ問題を見つけることです。
例:nを見つけるという階乗問題では、n!= N *(n-1)!,(N-1)!はn!の小さな副問題です。
2.除算時の変化量を調べる(再帰関数のパラメーター)
分割時の変化量は、元の問題から副問題に変化する過程であり、変化する人は誰でも変化量です。一般に、除算中の変化量は再帰関数のパラメーターです。
たとえば、nを見つけるという階乗問題では、nの値は連続的に減少しているため、再帰関数のパラメーターはnです。
3.口を見つける(再帰関数の終わり)
出口は再帰関数の終わりです。つまり、再帰関数はの呼び出しを停止する必要があります。
たとえば、nを見つけるという階乗問題では、nを負の数にすることはできないため、nの最小値は0にしかなりません。つまり、n = 0の場合、これは再帰関数の出口です。このとき、n = 0が取り込まれ、f(0)は0の階乗であり、結果は1です。
Ⅲ。典型的な例
上記の2つのパートで再帰関数の概念と再帰関数の使用法を説明しましたが、偽のハンドルを練習しない場合は、上記を使用していくつかの問題を解決しましょう。もちろん、問題の難しさは浅いものから深いものまでです。最初の解決策は、悩まされてきたnの階乗を見つける問題です。
1. nの階乗を見つける
この問題は、パートIIの順に解決します。
- 関数の定義:nの階乗を解くために関数f()を定義するため、パラメーターnを渡す必要があります。これは、この数値の階乗を見つけることを意味します。このとき、関数はf(int n)として定義されます。
- 問題を見つける:n!= N *(n-1)!n-1の階乗を解くためのロジックはnの場合と同じです。n-1のスケールはnより小さいため、n-1を解きます階乗は副問題です。
- 除算の変化量を求めます。部分問題を分割する過程で、nの値のみが連続的に減少していることがわかります。そのため、再帰関数の唯一のパラメーターはnです。
- 口を見つける:nは負でない数であることを知っているため、nが0の場合、これは再帰関数の出口です。この時点でf(0)、つまり0の階乗結果は1です。
したがって、nの階乗を解くための再帰関数コードは次のとおりです。
public class 求n的阶乘 {
//函数的意义:求解阶乘
//@param n:被求解阶乘的数
static int f(int n){
// 不符要求的情况
if(n < 0){
System.out.println("n不能为负数");
return 0;
}
// 出口
if(n==0){
return 1;
}
// 划分子问题
return n*f(n-1);
}
// 测试代码
public static void main(String[] args) {
System.out.println(f(10));
System.out.println(f(1)); // 特殊数值测试
System.out.println(f(0)); // 特殊数值测试
}
}
コンソール出力は次のとおりです。
2.配列の合計
配列の総和問題については、loopメソッドを使用して簡単に取得できます。この例は、再帰関数の設計方法を誰もがよりよく理解できるようにするためのものです。
覚えておいて、問題を解決するためにループを使用して、再帰を解決することができ、再帰の使用が解決することができ、サイクルが解決することができない場合があります。
分析は次のとおりです。
- 関数の定義:SumOfArray()関数の意味を定義して配列を合計するため、そのパラメーターを配列に渡す必要があります。したがって、現在の関数はSumOfArray(int [] array)として定義されます
- サブ問題を見つけるために:アレイの値と配列= +の最初の要素を検索し、残りの配列要素を求めます。残りの配列要素の合計を見つけることは、より小さな問題です。
- 除算中の変化量を見つける:副問題の除算中、配列インデックスの値は常に変化していることがわかったので、関数の定義、開始、配列の合計を示すために使用される新しいパラメーターを追加する必要がありますこの時点での関数である最初の要素のフッターは、SumOfArray(int [] array、int begin)として定義されます。
次に、2の分析と組み合わせると、次のような式が得られます。SumOfArray(配列、開始)=配列[開始] + SumOfArray(配列、開始+ 1)。 - 口を見つける:簡単に分析できます。beginが配列の最後の要素、つまりbegin = array.length-1になると、プロセス全体が終了します。このとき、要素が1つだけの配列を渡すのと同じであるため、返される結果は配列[beigin]です。
したがって、配列合計の再帰関数コードは次のとおりです。
public class 数组求和 {
static int SumOfArray(int[] arr, int begin){
// 出口
if(begin == arr.length-1){
return arr[begin];
}
// 重复
return arr[begin]+SumOfArray(arr, begin+1);
}
// 测试代码
public static void main(String[] args) {
System.out.println(SumOfArray(new int[]{1,2,3,4,5,6},0));
}
}
コンソールの結果は次のとおりです。
*例1と例2の昇華
質問1と例2を簡単に思い出してみましょう。質問1と例2には、元の問題=直接量+小さな副問題という分割機能があることがわかります。
nを因数分解すると、n!= N *(n-1)!またはf(n)= n * f(n-1)、nは直接量、(n-1)!より小さなスケールです副問題。
同様に、配列の合計問題では、SumOfArray(配列、開始)=配列[開始] + SumOfArray(配列、開始+ 1)、配列[開始]は直接量、SumOfArray(配列、開始+ 1)は小さな副問題。
次の例では難易度が高くなり、除算方法は同じではなくなります。学び続けましょう、さあ!
3.フィボナッチ数列
ゴールデンセクションシーケンスとも呼ばれるフィボナッチシーケンス。再帰関数を使用して、フィボナッチ数列のN番目の項の値を見つけます。もちろん、フィボナッチ数列を理解していない場合、この問題を解決する方法はないので、最初にフィボナッチ数列を理解しましょう。
フィボナッチ数列は、F(1)= 1、F(2)= 1で特徴付けられる一連の数値で、3番目の項目から始まり、各項目の値は最初の2つの項目の合計、つまりF(n)= F(n-1)+ F(n-2)、(n≥3、n∈N *)。数字のセットを使用して、より視覚的に説明します。1、1、2、3、5、8、13 、......
フィボナッチ数列の導入は終わりました。真実に戻り、以前の設計方法を使用して再帰を設計します機能。
- 関数を定義する:F()をn番目の項目の値として定義するため、項目番号nをパラメーターとして渡す必要があります。この時点では、関数はF(int n)として定義されています。
- 副問題の発見:はじめに、元の問題を副問題に分割する方法を見つけましたが、もちろん、F(n)= F(n-1)+ F(n-2)という形で現れます。問題例1と問題例2の分割とは異なることに気付いたことがありますか?ここでの分割は、元の問題を2つの小さなサブ問題に分解することです。
- 分割するときの変化量を見つける:項目の数が唯一の変化量であることは簡単にわかるので、項目数nが唯一のパラメーターです。
- 確認: nは正の整数に属しており、入力パラメーターを0または負にすることはできません。したがって、n = 1またはn = 2の場合、それは関数の出口です。この時点で元の関数を使用すると、結果は1になります。
要約すると、フィボナッチ数列のn番目の値を再帰的に解決するためのコードは次のとおりです。
public class 求解斐波那契数列第n项的值 {
static int F(int n){
// 限制项数为正整数
if(n <= 0){
System.out.println("项数不能为0或负数");
return -1;
}
// 出口
if(n==1 || n==2)
return 1;
// 划分的子问题
return F(n-1) + fib(n-2);
}
public static void main(String[] args) {
System.out.println(F(10));
}
}
コンソール出力は次のとおりです。
4.ハノイタワー問題
問題例3では、元の問題を分割することで、直接的な量と小さな副問題に分割できるだけでなく、複数の副問題にも分割できることがわかります。この例では、思考の分割がさらに昇華し、さらに同等の思考が必要です。問題を解決する前に、ハノイタワーの問題が何であるかを理解する必要があります。
ハノイタワー(ハノイタワーとも呼ばれる)の問題は、インドの古代の伝説に端を発する教育玩具です。ブラフマーが世界を創造したとき、彼は3本のダイヤモンドの柱を作りました。ブラフマーはブラフミンに、円盤を別の柱の上にサイズの順に下から再配置するよう命じました。また、小さなディスクではディスクを拡大できず、一度に3本の支柱の間を移動できるディスクは1つだけであると規定されています。以下に示すように、3次ハノイタワー問題のダイアグラムを例に取ります。
OK、ハノイタワーの問題はすでにわかっているので、再帰的思考を使用して、異なる順序のハノイタワー問題の解決手順を示し、すべてのディスクがルールを満たすという前提の下でAからAに移動するようにします。 C.
-
定義関数:HanoiTower()の意味は、補助層を使用して、N層ハノイタワーを開始列から終了列に移動することです。したがって、ディスクの数(レイヤー)N、原点(開始バー)、ヘルプ(補助バー)、宛先(エンドバー)が必要です。この時点で、関数はHanoiTower(int N、文字列の起点、文字列のヘルプ、文字列の宛先)として定義されています。
-
問題の発見(強調、注意して検討してください):Hanotaの問題は、Cの助けを借りて1〜N個のディスク(全体として)をAからBに移動することです。これを3つのステップに分割できます。
- 1からN-1枚のディスクを(全体として)(現時点ではAで)Cで移動してAからBに移動します。
- N番目のディスク(現時点ではA)をAからCに移動します。
- BからCに移動するには、Aで1〜N-1枚のディスク(全体として)(この時点ではBで)を移動します。
注意深く観察すると、ステップ1と3は実際には元の問題の小さな分割であることがわかります。どちらも、特定の数のディスクを(全体として)1列ずつ別の列に移動します。これは、同等の考え方を使用したサブ問題の分割です。
-
変化の量を見つける:分割プロセス全体で、変化の量はディスクの数Nと異なる列です。
-
口を見つける:ディスクが1つしかない場合は、ハノイタワー問題の出口です。この時点で機能を導入することは、ディスクが1つしかないハノイの塔の問題と同じです。