ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Recursion 재귀
    Algorithm/Theory 2020. 6. 11. 12:21
    728x90

    많은 일들은 대개 작은 조각들로 나누어 볼 수 있습니다. 그리고 한 조각이 작으면 작을수록 일은 단순해지고 반복되는 형태를 띄는 경우가 많습니다. 완전히 같은 코드를 반복적으로 하는 작업이 있다면 이때 재귀 함수(recursion fnction), recursion(재귀 호출)이 유용하게 사용될 수 있습니다.

    1~100까지의 합을 구하는 경우를 재귀적으로 생각해봅시다.

    1~100까지의 합은 100 + 1~99까지의 합으로 나눠서 생각할 수 있습니다.
    마찬가지로 1~99까지의 합은 99 + 1~98까지의 합으로 볼 수 있겠죠.
    "n + 1~n-1까지의 합" 이라는 과정은 완벽하게 동일합니다.
    이를 코드로 구현해보면,

    func sum(_ n: Int) -> Int {
      if n == 1 { return 1 }
      return n + sum(n-1)
    }

    n이 아무리 커지더라도 몇 줄의 코드로 간단하게 해결할 수 있습니다. n이 커지면 커질수록 재귀함수는 더욱 효율적이겠죠. 이때 주목할 건 첫 줄의 if문입니다. n이 1일 때 1을 리턴해주지 않으면 이 반복문은 끝나지 않습니다. 이처럼 재귀 함수가 가장 작은 단계까지 가서 더 이상 쪼개지지 않고 종료되는 재귀 호출을 기저 사례(base case)라고 합니다. 

    기저 사례를 선택할 때는 존재하는 모든 입력이 항상 기저 사례의 답을 이용해 계산될 수 있도록 해야합니다.
    (가장 작은 조각을 선택해야 됩니다.)
    위 경우에서 n이 1인 경우가 아니라 n이 2인지 확인해서 3을 리턴하면 2보다 큰 경우는 잘 동작하겠지만 2보다 작아지는 경우는 문제가 생깁니다.

    모든 반복문은 재귀 함수로 만들 수 있습니다. 당연히 그 역도 성립하지요. 0부터 번호가 매겨진 n개의 원소 중 4개를 고르는 모든 경우의 수를 출력하는 코드를 작성하려면 어떻게 해야할까요? 4중 for문을 사용하면 간단하게 해결할 수 있습니다.

    for i in 0..<n {
      for j in i..<n {
        for k in j..<n {
          for l in k..<n {
    	print("(\(i), \(j), \(k), \(l)")        
          }
        }
      }
    }

    하지만 5개, 10개, 100개를 골라야 한다면 어떻게 될까요?

    처참한 상황을 막기 위해 재귀적으로 바꿔보죠.
    먼저 for문이 하는 일부터 생각해볼까요?

    원하는 만큼 수를 선택했으면 출력합니다. 끝입니다.

    이제 반복을 위해 무엇이 필요한지 생각해볼까요?

    • 원소들의 총 개수

    • 더 골라야하는 수

    • 지금까지 고른 수

    이제 마지막으로 기저 사례를 생각해봅시다.

    • 원하는 만큼 선택됐는지 확인합니다. --> 더 이상 선택할 수 있는 수가 없는 경우로 생각할 수 있습니다.

    이제 이를 구현해보면,

    for pick(n: Int, picked: [Int], toPick: Int) {
      if toPick == 0 { return print("\(picked)") }
      // 고를 수 있는 가장 작은 수
      let smallest = picked.isEmpty ? 0 : picked.last! + 1
      // 가장 작은 수 부터 고를 수 있는 모든 경우를 출력
      for s in s..<n {
        var newPicked = picked
        newPicked.append(s)
        pick(n: n, picked: newPicked, toPick: toPick -1)
      }
    }

    역시 반복이 많아지는 경우를 생각해본다면 매우 간단한 코드로 구현할 수 있습니다.

     

    요약해보면, 역시 재귀의 핵심은 기저사례를 제대로 찾을 수 있는 경우인지 확인하는 일입니다. 기저사례가 있는 반복수행이 가능한 일이여만 재귀적으로 수행할 수가 있습니다. 그래야 명확한 종료시점까지 같은 일을 반복할 수 있기 때문이죠.

     

    감사합니다. 이번 포스팅은 알고리즘 문제해결전략의 설명이 너무 좋아 많이(거의) 참고해서 사용했습니다.

     

     

     

    참고: 종만북 (알고리즘 문제해결전략)

     

    'Algorithm > Theory' 카테고리의 다른 글

    Binary Search 이진탐색  (0) 2020.06.12
    Permutation 순열  (0) 2020.06.11
    위상정렬 Topological Sort  (0) 2020.06.07
    합병정렬 Merge sort  (0) 2020.05.14
    프림 알고리즘  (0) 2020.03.06

    댓글

Designed by Tistory.