본문 바로가기

알고리즘

재귀 함수

재귀 함수란?

  • 함수가 자기 자신을 호출하는 프로그래밍 기법, 복잡한 연산을 동일한 형태의 작은 부분으로 나누어서 해결하는 방법이다.
  • 재귀 함수는 가장 최근에 호출한 함수부터 연산을 하는 스택(Stack) 구조를 가진다.
  • 재귀 사례(Recursive Case)를 기준으로 문제를 세분화하고 기저 사례(Base Case)를 종료 조건으로 한다.
    • 재귀 사례: 재귀 함수에서 자기 자신을 호출하여 반복하는 부분
    • 기저 사례: 재귀 함수의 실행 중에 더 이상 쪼갤 수 없는 조건

예시 문제 - 하노이의 탑

  • 하노이의 탑은 여러 크기의 원판을 큰 원판이 작은 원판의 위로 가지 않도록 하여 한 번에 한 개의 원판을 움직여서 다른 기둥으로 이동을 시키는 문제로 재귀 함수를 공부할 때 자주 사용하는 문제이다.
  • 문제 링크: https://www.acmicpc.net/problem/11729
    • 설명: 세 개의 장대가 있고 첫 번째 장대에는 반경이 서로 다른 n개의 원판이 쌓여 있다. 각 원판은 반경이 큰 순서대로 쌓여있다. 이제 수도승들이 다음 규칙에 따라 첫 번째 장대에서 세 번째 장대로 옮기려 한다.
      1. 한 번에 한 개의 원판만을 다른 탑으로 옮길 수 있다.
      2. 쌓아 놓은 원판은 항상 위의 것이 아래의 것보다 작아야 한다.이 작업을 수행하는데 필요한 이동 순서를 출력하는 프로그램을 작성하라. 단, 이동 횟수는 최소가 되어야 한다.
    • 입력: 첫째 줄에 첫 번째 장대에 쌓인 원판의 개수 N이 주어진다.
    • 출력: 첫째 줄에 옮긴 횟수 K를 출력한다. 두 번째 줄부터 수행 과정을 출력한다. 두 번째 줄부터 K개의 줄에 걸쳐 두 정수 A B를 빈칸을 사이에 두고 출력하는데, 이는 A번째 탑의 가장 위에 있는 원판을 B번째 탑의 가장 위로 옮긴다는 뜻이다.

예시 풀이

  • 재귀 사례와 기저 사례를 결정하는 것이 중요함.
  • 이동 경로 (n = 3의 경우)

  • 재귀 사례
    1. n이하의 원판들을 시작 기둥에서 보조 기둥으로 이동
    2. 가장 큰 원판 n을 시작 기둥에서 목표 기둥으로 이동
    3. n이하의 원판들을 보조 기둥에서 목표 기둥으로 이동
  • 기저 사례
    • 가장 작은 원판(n = 1)을 시작 기둥에서 목표 기둥으로 이동
  • 기능 구현
import sys

N = int(sys.stdin.readline()) # 데이터 입력


def hanoi(): # 함수 정의
	pass


hanoi(N) # 함수 실행
import sys

N = int(sys.stdin.readline())


def hanoi(n, start, target, sub): # 입력 변수 정의(탑의 층 수, 시작 기둥, 목표 기둥, 보조 기둥)
	if n == 1: # 기저 사례 정의
		print(f'원판 {n}을 {start}에서 {target}으로 이동')
		return


hanoi(N, 1, 3, 2)
import sys

N = int(sys.stdin.readline())


def hanoi(n, start, target, sub):
	if n == 1:
		print(f'원판 {n}을 {start}에서 {target}으로 이동')
		return
	
	hanoi(n-1, start, sub, target) # 재귀 사례 1
	
	print(f'원판 {n}을 {start}에서 {target}으로 이동') # 재귀 사례 2
	
	hanoi(n-1, sub, target, start) # 재귀 사례 3


hanoi(N, 1, 3, 2)

 

  • 정답 출력
import sys

N = int(sys.stdin.readline())


def hanoi(n, start, target, sub, move=0): # 이동 횟수를 계산하기 위한 변수 추가
	if n == 1:
		print(f'원판 {n}을 {start}에서 {target}으로 이동')
		move += 1 # 원판 이동횟수 추가
		return move # 원판 이동횟수를 반환하여 이동 횟수를 추적
	
	move = hanoi(n-1, start, sub, target, move)
	
	print(f'원판 {n}을 {start}에서 {target}으로 이동')
	move += 1 # 원판 이동횟수 추가
	
	move = hanoi(n-1, sub, target, start, move)
	
	return move # 원판 이동횟수를 반환하여 이동 횟수를 추적


print(hanoi(N, 1, 3, 2))
import sys

N = int(sys.stdin.readline())


def hanoi(n, start, target, sub, move=0, path=[]): # 이동 경로 추적을 위한 리스트 추가
	if n == 1:
		path.append((start, target)) # 이동 경로를 함수 내에서 출력하지 않고 리스트에 입력
		move += 1
		return move, path
	
	move, path = hanoi(n-1, start, sub, target, move, path)
	
	path.append((start, target)) # 이동 경로를 함수 내에서 출력하지 않고 리스트에 입력
	move += 1
	
	move, path = hanoi(n-1, sub, target, start, move, path)
	
	return move, path


move, path = hanoi(N, 1, 3, 2)

print(move) # 이동 횟수 출력

for i, j in path: # 경로 출력
	print(i, j)

 

  • 코드 개선
import sys

N = int(sys.stdin.readline())


def hanoi(n, start, target, sub, path=[]): # move 제거
	if n == 1:
		path.append((start, target))
		return path

	path = hanoi(n-1, start, sub, target, path)

	path.append((start, target))

	path = hanoi(n-1, sub, target, start, path)

	return path


print(2**N - 1) # N층 하노이의 탑의 이동 횟수

path = hanoi(N, 1, 3, 2)

for i, j in path:
	print(i, j)
import sys

N = int(sys.stdin.readline())


def hanoi(n, start, target, sub): # path 제거
	if n == 1:
		print(start, target) # 실행 과정에서 이동 경로를 출력하도록 변경
		return

	hanoi(n-1, start, sub, target)

	print(start, target)

	hanoi(n-1, sub, target, start)


print(2**N - 1)

hanoi(N, 1, 3, 2)

 

  • 재귀 흐름 (n=3인 경우)
def hanoi(n, start, target, sub):
	if n == 1:
		print(start, target)
		return

	hanoi(n-1, start, sub, target)

	print(start, target)

	hanoi(n-1, sub, target, start)
  1. 함수 호출(hanoi(3, 1, 3, 2))
    1. 함수 호출(hanoi(2, 1, 2, 3))
      1. 함수 호출(hanoi(1, 1, 3, 2))
      2. 초록 원판을 1에서 3으로 이동
    2. 파랑 원판 이동을 1에서 2로 이동
      1. 함수 호출(hanoi(1, 3, 2, 1))
      2. 초록 원판을 3에서 2로 이동
  2. 빨강 원판을 1에서 3으로 이동
    1. 함수 호출(hanoi(2, 2, 3, 1))
      1. 함수 호출(hanoi(1, 2, 1, 3))
      2. 초록 원판을 2에서 1로 이동
    2. 파랑 원판을 2에서 3으로 이동
      1. 함수 호출(hanoi(1, 1, 3, 2))
      2. 초록 원판을 1에서 3으로 이동
  3. 종료