404 Not Found

404 Not Found


nginx

再帰

再帰とは、関数が自分自身を呼び出すことです。

変ですね――関数がどうやって自分自身を呼び出すんですか? 無限ループになってしまいませんか?

適切な基本ケースを記述さえすれば、再帰は完璧に機能し、ループでは記述が難しい問題も解決できます。


1. 再帰とは何か?

(1) 1.1 現実世界との類推

日常のシーン 再帰的な機能
向かい合った2枚の鏡が、互いの姿を映し合っている 自分自身を呼び出す関数
ロシアのマトリョーシカ(一つ開けると、中にまた一つ入っている) 問題を、同じ問題のより小さな形に分解すること

再帰の核心的な考え方: 大きな問題を、直接解けるほど小さくなるまで、同じ問題のより小さなバージョンに分割していくこと。

(2) 1.2 2つの要件

すべての再帰関数には、以下の要素が必須です:

  1. 基本ケース: 再帰を停止させる条件(最も単純なケース)
  2. 再帰的なケース: 問題を、それ自体のより小さなバージョンに分解する部分


2. 再帰の例:階乗

(1) 2.1 階乗の定義

TEXT
n! = n × (n-1) × (n-2) × ... × 1

再帰的定義:

TEXT
n! = n × (n-1)! (recursive case)
0! = 1 (base case)

(2) ▶ サンプル:1:再帰を用いた階乗の計算(難易度 ⭐⭐)

CPP
#include iostream

// Recursive function: calculate n!
long long factorial(int n) {
 // Base case
 if (n == 0) {
 return 1;
 }
 // Recursive case
 return n * factorial(n - 1);
}

int main() {
 int n;
 std::cout << "Enter a non-negative integer: ";
 std::cin >> n;
 
 std::cout << n << "! = " << factorial(n) << std::endl;
 
 return 0;
}
▶ 試してみよう

実行例:

TEXT
Enter a non-negative integer: 5
5! = 120

(2) 2.2 再帰呼び出しトレース (n=5)

TEXT
factorial(5)
= 5 × factorial(4)
= 5 × 4 × factorial(3)
= 5 × 4 × 3 × factorial(2)
= 5 × 4 × 3 × 2 × factorial(1)
= 5 × 4 × 3 × 2 × 1 × factorial(0)
= 5 × 4 × 3 × 2 × 1 × 1 ← Base case reached, start returning
= 5 × 4 × 3 × 2 × 1
= 5 × 4 × 3 × 2
= 5 × 4 × 6
= 5 × 24
= 120


3. 再帰の例:フィボナッチ数列

(1) 3.1 定義

フィボナッチ数列は 0、1 から始まり、その後の各数は前の 2 つの数の和となります:

TEXT
F(0) = 0 (base case)
F(1) = 1 (base case)
F(n) = F(n-1) + F(n-2) (recursive case, n ≥ 2)

▶ サンプル 2: 再帰を用いたフィボナッチ数列 (難易度 ⭐⭐)

CPP
#include iostream

// Recursive function: calculate the nth Fibonacci number
int fibonacci(int n) {
 // Base cases
 if (n == 0) return 0;
 if (n == 1) return 1;
 // Recursive case
 return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
 int n;
 std::cout << "Enter n: ";
 std::cin >> n;
 
 std::cout << "F(" << n << ") = " << fibonacci(n) << std::endl;
 
 return 0;
}
▶ 試してみよう

実行例:

TEXT
Enter n: 10
F(10) = 55
⚠️ 注意: この再帰的なフィボナッチアルゴリズムは、同じ値を何度も再計算してしまうため、n が大きい場合(例えば n > 40 など)には 非常に処理が遅くなります。これは「指数関数的な計算量」と呼ばれます。後で、この問題を解決するための動的計画法について学びます。



4. 再帰とループ

側面 再帰 ループ
コードスタイル 洗練されていて簡潔 わかりやすい
読みやすさ 自然な再帰的な問題に最適 単純な繰り返し処理に最適
パフォーマンス 関数呼び出しによるオーバーヘッド 高速(呼び出しのオーバーヘッドなし)
メモリ コールスタックを使用する(スタックオーバーフローのリスクあり) メモリ使用量が少ない
適している用途 木構造の探索、分割統治法、バックトラッキング 単純な反復処理

▶ サンプル 3: ループと再帰 — カウントダウン (難易度 ⭐)

ループ版:

CPP
void countDownLoop(int n) {
 for (int i = n; i > 0; i--) {
 std::cout << i << "... ";
 }
 std::cout << "Liftoff!" << std::endl;
}
▶ 試してみよう

再帰的なバージョン:

CPP
void countDownRecursive(int n) {
 if (n == 0) { // Base case
 std::cout << "Liftoff!" << std::endl;
 return;
 }
 std::cout << n << "... ";
 countDownRecursive(n - 1); // Recursive call
}
💡 ヒント: 再帰的なバージョンはより洗練されていますが、メモリを多く消費します(呼び出しごとにスタックにフレームが追加されるため)。単純な反復処理にはループを使用し、本質的に再帰的な問題(木構造の探索など)には再帰を使用しましょう。



5. 再帰におけるよくある落とし穴

(1) 5.1 基本ケースがない場合 = 無限再帰

CPP
void infiniteRecursion() {
 // ❌ No base case! This will run until the stack overflows
 std::cout << "Going deeper..." << std::endl;
 infiniteRecursion();
}

結果: プログラムは スタックオーバーフロー エラーでクラッシュします。

(2) 5.2 再帰が深すぎる

再帰呼び出しが行われるたびに、スタックメモリが消費されます。再帰が深くなりすぎると(たとえば、10万回の呼び出しなど)、スタックオーバーフローが発生します。

💡 ヒント: 経験則として、再帰の深さが 10,000 を超えるとリスクが高くなります。再帰が非常に深くなる場合は、反復処理による解決策に変換してください。


❓ よくある質問

Q どんなループでも再帰的に書き換えることはできますか?
A はい、どんなループでも再帰的に書き換えることができます。しかし、すべての再帰がループであるべきというわけではありません。木構造の探索など、本来再帰的な性質を持つ問題もあり、それらを反復処理で実装するのははるかに困難です。
Q 再帰とループ、どちらが速いですか?
A ループの方が速いです。関数呼び出しのオーバーヘッドがないからです。しかし、多くの問題(フィボナッチ数列など)では、再帰的な実装の方が理解しやすく、記述も簡単です。パフォーマンスが重要な場合は、後で最適化を行うことができます。

📖 まとめ


📝 練習問題

  1. 初心者(難易度 ⭐): 1 から n までの数の和を返す再帰関数 int sum(int n) を作成してください。n = 100 でテストしてください。

  2. 中級(難易度 ⭐⭐): 底^exp を計算する再帰関数 int power(int base, int exp) を作成してください。例えば、power(2, 10) は 1024 を返す必要があります。

  3. 上級(難易度 ⭐⭐⭐): ハノイの塔の問題を解く再帰関数を作成してください。各移動を「ディスク1をAからCへ移動」といった形式で出力してください。ディスクの数はユーザーが入力します。



6. 🚀 次は

再帰は強力なツールです。次は、関数のオーバーロード(第13課)について学びます。これは、同じ名前でパラメータが異なる複数の関数を定義する方法です。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%