Post

자료구조 2. 재귀

코드

  • 모든 코드는 깃허브에서 확인하실 수 있습니다.

재귀(recursion)

  • 자기 자신을 호출하여 문제를 해결하는 방법

  • 수학적 귀납법과 비슷

  • 예) 팩토리얼

    \[n! = \begin{cases} 1 & n=0\\ n*(n-1)! & n \ge 1 \end{cases}\]
    • \(5!\)은 \(5 \times (5 - 1)!\)으로 표현할 수 있고 다시 \(5 \times 4 \times (4 - 1)!\), \(5 \times 4 \times 3 \times (3 - 1)!\), \(5 \times 4 \times 3 \times 2 \times (2 - 1)!\), \(5 \times 4 \times 3 \times 2 \times 1\)처럼 표현할 수 있음

재귀 함수

  • 자기 자신을 호출하는 함수

  • 예) 팩토리얼 함수

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    int FactorialRecursive(int n)
    {
    	if (n == 1)
    	{
    		return 1;
    	}
      
    	return n * FactorialRecursive(n - 1);
    }
    
  • 재귀 함수의 필수 요소
    • 종료 조건
      • 종료 시점을 결정
      • 만약 없다면 계속 끝나지 않고 계속 호출됨(결국 런타임 에러 발생)
    • 재귀 호출
      • 재귀적으로 호출을 하지 않는다면 재귀 함수가 아님(당연)
  • 재귀 함수 사고법

    • 함수를 완성하지 않았더라도 ‘이 함수는 이런 역할을 할거야’라고 생각하고 사용

    • 큰 문제를 쪼개기

  • 재귀 함수의 단점 및 주의점
    • 같은 계산을 여러번 할 수도 있음

        graph TD;
            fibo51["fibo(5)"] --> fibo41["fibo(4)"];
            fibo51 --> fibo31["fibo(3)"];
            fibo41 --> fibo32["fibo(3)"];
            fibo41 --> fibo21["fibo(2)"];
            fibo32 --> fibo22["fibo(2)"];
            fibo32 --> fibo11["fibo(1)"];
            fibo22 --> fibo12["fibo(1)"];
            fibo22 --> fibo01["fibo(0)"];
            fibo31 --> fibo23["fibo(2)"];
            fibo31 --> fibo13["fibo(1)"];
            fibo23 --> fibo14["fibo(1)"];
            fibo23 --> fibo02["fibo(0)"];
            
      
      • 피보나치 수열(1, 1, 2, 3, 5, 8, 13, …)의 경우 위 그림처럼 같은 계산을 여러번 진행함
      • stack overflow가 발생할 수도 있음
      • 이러한 문제는 반복문을 사용하거나 다이나믹 프로그래밍 알고리즘을 통해 해결 가능
    • 캐싱 없이 간단한 반복문으로 해결할 수 있는 문제는 반복문 사용
      • 예) 1 ~ N까지 더하기, 팩토리얼 구하기
    • 그 외에는 우선 재귀 함수로 작성 후 필요한 경우 반복문으로 리팩토링
      • 재귀 함수를 사용하면 작성 속도가 빠르고 간편

재귀 함수를 이용한 하노이의 탑

하노이의 탑

tower_of_hanoi

  • 브라흐마의 규칙
    • 한 번에 원판 하나씩만 옮길 수 있음
    • 작은 원판 위에 그보다 큰 원판이 올 수 없음
  • 하노이의 탑
    • 막대가 3개 있음
    • 막대 하나에 n개의 원판으로 구성된 탑이 있음
    • 탑을 다른 막대에 이동시켜야 함

하노이의 탑 사고법

  • 막대가 세 개가 있고, 한 막대에 n개의 원판이 있음
    • n개의 원판에서 상위 n-1개를 다른 막대에 옮길 수 있다고 가정
      • n-1개의 원판을 중간 막대로 옮김
      • 마지막 n번째 원판을 목적지 막대에 옮김
      • 중간 막대에 있던 n-1개의 원판을 목적지 막대에 옮김
  • 막대가 세 개가 있고, 한 막대에 n-1개의 원판이 있음
    • n-1개의 원판에서 상위 n-2개를 다른 막대에 옮길 수 있다고 가정
      • n-2개의 원판을 중간 막대로 옮김
      • 마지막 n-1번째 원판을 목적지 막대에 옮김
      • 중간 막대에 있던 n-2개의 원판을 목적지 막대에 옮김
  • 위와 같이 재귀적으로 생각하면 결국 마지막에는 1개의 원판을 이동시키는 문제가 됨
    • 이렇게 큰 문제를 작은 문제로 나눠 생각하면 간편함

c언어 코드

  • 코드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    void TowerOfHanoiRecursive(int countDisc, char start, char temp, char end)
    {
    	if (countDisc == 1)
    	{
    		PrintMovingDisc(countDisc, start, end);
      
    		return;
    	}
      
    	TowerOfHanoiRecursive(countDisc - 1, start, end, temp);
    	PrintMovingDisc(countDisc, start, end);
    	TowerOfHanoiRecursive(countDisc - 1, temp, start, end);
    }
    
  • 결과

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    1: A -> B
    2: A -> C
    1: B -> C
    3: A -> B
    1: C -> A
    2: C -> B
    1: A -> B
    4: A -> C
    1: B -> C
    2: B -> A
    1: C -> A
    3: B -> C
    1: A -> B
    2: A -> C
    1: B -> C
    
This post is licensed under CC BY 4.0 by the author.