아래의 내용은 java_grammer 레파지토리 C02MethodClass 디렉터리에 저장되어있는 내용을 정리함
기본적으로 재귀는 “들어가고(push), 나오는(pop)” 흐름임.
그걸 눈으로 직접 확인해보는 게 핵심.
package C02MethodClass;
import java.util.ArrayList;
import java.util.List;
public class C11RecursiveBasicFlow {
public static void main(String[] args) {
// recur0(0, 3);
// recur1(0, 3);
recur2(new ArrayList<>(), 0, 3);
}
public static void recur0(int count, int target) {
if (count == target) return;
System.out.println("재귀 호출 전 count : " + count);
recur0(count + 1, target);
System.out.println("재귀 호출 후 count : " + count);
}
흐름
| 단계 | 호출 방향 | 복귀 방향 |
|---|---|---|
| 1 | count=0 호출 전 출력 | |
| 2 | count=1 호출 전 출력 | |
| 3 | count=2 호출 전 출력 | |
| 4 | count=3 → 종료 | |
| count=2 호출 후 출력 | ||
| count=1 호출 후 출력 | ||
| count=0 호출 후 출력 |
즉, “아래로 들어갔다 위로 올라온다” 흐름임.
public static void recur1(int count, int target) {
if (count == target) return;
System.out.println("재귀 호출 전 count : " + count);
count += 1;
recur1(count, target);
System.out.println("재귀 호출 후 count : " + count);
}
recur0()이랑 비슷하지만 값 증가를 내부에서 처리함.
복귀 시 찍히는 count 값이 달라짐.
public static void recur2(List<Integer> myList, int count, int target) {
if (myList.size() == target) return;
myList.add(count);
recur2(myList, count + 1, target);
System.out.println(myList);
myList.remove(myList.size() - 1);
}
List는 참조형이라 호출 사이에서도 같은 객체 공유함. 흐름
호출: [0] → [0,1] → [0,1,2]
복귀: [0,1,2] → [0,1,2] → [0,1,2]
누적합, 팩토리얼, 피보나치가 가장 기본임.
반복문이랑 재귀 비교하면 확실히 감 잡힘.
package C02MethodClass;
import java.util.Arrays;
public class C12RecursiveExample {
public static void main(String[] args) {
int sumAcc = 0;
for (int i = 1; i <= 10; i++) sumAcc += i;
System.out.println(sumAcc);
int recSumAcc = sumAcc(1, 10);
System.out.println(recSumAcc);
int factorialByFor = 1;
for (int i = 1; i <= 5; i++) factorialByFor *= i;
System.out.println(factorialByFor);
System.out.println(factorial(5));
int n1 = 1, n2 = 1, n3 = 0;
for (int i = 2; i < 11; i++) {
n3 = n1 + n2;
n1 = n2;
n2 = n3;
}
System.out.println(n3);
int[] dp = new int[10];
dp[0] = dp[1] = 1;
for (int i = 2; i < 10; i++) dp[i] = dp[i - 1] + dp[i - 2];
System.out.println(Arrays.toString(dp));
System.out.println(fibonacci(10));
}
public static int sumAcc(int start, int end) {
if (start > end) return 0;
return start + sumAcc(start + 1, end);
}
흐름
스택 해제
f(10)=10
f(9)=19
f(8)=27
...
f(1)=55
재귀는 맨 아래까지 갔다가 위로 쌓아올림.
public static int factorial(int n) {
if (n == 1) return 1;
return n * factorial(n - 1);
}
흐름
f(5)=5*f(4)
f(4)=4*f(3)
f(3)=3*f(2)
f(2)=2*f(1)
f(1)=1
결과 → 120
public static int fibonacci(int n) {
if (n <= 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
점화식: f(n)=f(n-1)+f(n-2)
직관적이지만 중복 호출 많음.
복잡도 O(2ⁿ).
→ DP로 풀면 효율적임.
재귀는 항상 세 단계로 나뉨.
핵심은 “언제 쌓이고, 언제 풀리는지” 보는 것.
System.out.println() 위치 바꿔가면서 직접 찍어보면 금방 감 옴.