다라다라V
article thumbnail
728x90
반응형

📌 재귀 알고리즘의 기본

📚 재귀(recursive) 알아보기

  • 어떤 이벤트에서 자기 자신을 포함하고 다시 자기 자신을 사용하여 정의되는 경우
  • 재귀를 효과적으로 사용하면 프로그램을 간결하고 효율성 있게 작성

 

📚 팩토리얼

  • = 순차 곱셈
  • 팩토리얼 : 양의 정수를 순서대로 곱한다는 의미

팩토리얼 n! 의 정의 (n은 양의 정수)

  • 0! = 1
  • n > 0이면 n! = n * (n - 1)!
# 양의 정수 n의 팩토리얼 구하기
def factorial(n: int) -> int:
    """양의 정수 n의 팩토리얼 값을 재귀적으로 구함"""
    if n > 0:
        return n * factorial(n - 1)
    else:
        return 1

if __name__ == '__main__':
    n = int(input('출력할 팩토리얼 값을 입력하세요: '))
    print(f'{n}의 팩토리얼은 {factorial(n)}입니다')
  • 매개변수 n에 전달받은 값이 0보다 크면 n * factorial(n - 1) 값을 반환
  • 그렇지 않으면 1을 반환
  • 파이썬에서는 표준 라이브러리인 math 모듈에서 factorial( ) 함수를 제공

 

📃 재귀 호출

  • factorail( ) 함수는 n - 1의 팩토리얼 값을 구하기 위해 다시 자신과 똑같은 factorial( ) 함수를 호출
  • 이러한 함수의 호출을 재귀 호출(recursive call)

 

📃 직접 재귀와 간접 재귀

  • 직접(direct) 재귀 : 자신과 똑같은 함수를 호출
  • 간접(indirect) 재귀 : a( ) 함수가 b( )함수를 호출하고, 다시 b( ) 함수가 a( ) 함수를 호출

 

📚 유클리드 호제법 알아보기

  • 두 정숫값의 최대 공약수를 재귀적으로 구하는 방법
  • 최대공약수 = GCD = Greatest Common Divisor

유클리드 호제법 - 위키백과, 우리 모두의 백과사전

예를 들어 두 정수 x, y의 최대 공약수(gcd(x, y))를 구하고 싶은 경우

  • x = az, y = bz 이며, a, b와 최대 정수 z가 존재할 때 z = gcd(x, y)
    • y = 0 : x
    • y ≠ 0 : gcd(y, x%y)
# 유클리드 호제법으로 최대 공약수 구하기
def gcd(x: int, y: int) -> int:
    """정숫값 x와 y의 최대 공약수를 반환"""
    if y == 0:
        return x
    else:
        return gcd(y, x % y)

if __name__ == '__main__':
    print('두 정숫값의 최대 공약수를 구합니다')
    x = int(input('첫 번재 정숫값을 입력하세요: '))
    y = int(input('두 번재 정숫값을 입력하세요: '))
    print(f'두 정숫값의 최대 공야굿는 {gcd(x, y)}입니다')

 

 

📌 재귀 알고리즘 분석

📚 재귀 알고리즘의 2가지 방법

recur( ) 라는 재귀 함수를 작성하여 재귀 알고리즘에 대해 알아봅시다

# 순수한 재귀 함수 구현하기

def recur(n: int) -> int:
    """순수한 재귀 함수 recur의 구현"""
    if n > 0:
        recur(n - 1)
        print(n)
        recur(n - 2)

x = int(input('정숫값을 입력하세요: '))

recur(x)
  • 재귀 호출을 여러 번 실행하는 함수
  • 재귀 호출하는 함수를 하향식(top-down)과 상향식(bottom-up) 방법으로 분석

 

📃 하향식 분석

  1. 왼쪽 아래 화살표를 따라 갑니다
  2. 되돌아오면 초록색 상자 안의 값을 출력합니다
  3. 오른쪽 아래 화살표를 따라 갑니다
  • 위의 분석 방법이 하향식 분석(top-down analysis)
    • 가장 위쪽에 위치한 상자의 함수 호출부터 시작하여 계단식으로 조사
    • 꼭대기부터 분석하면 같은 함수를 여러번 호출하므로 효율적이지 않을 수 있음

 

📃 상향식 분석

  • 하향식 분석과 반대로 아래쪽부터 쌓아 올리며 분석하는 방법
  • bottom-up analysis

📃 꼬리 재귀 제거하기

  • recur() 함수의 뒤쪽 recur(n-2)를 변경해 꼬리 재귀를 제거
  • n의 값을 n - 2로 업데이트하고 함수의 시작지점으로 돌아가도록 함
# 비재귀적으로 재귀 함수 구현하기(꼬리 재귀를 제거)

def recur(n: int) -> int:
    """꼬리 재귀를 제거한 recur() 함수"""
    while n > 0:
        recur(n - 1)
        print(n)
        n = n- 2

x = int(input('정숫값을 입력하세요: '))

recur(x)

 

📃 재귀를 제거하기

  • 앞선 논리를 활용하여 recur(n - 1)을 제거할 수는 없습니다
  • n 값을 n - 1로 업데이트하고 함수 시작 지점으로 돌아갑니다 (X)
  • 현재의 n 값을 임시로 저장해서 출력해야하는 문제가 발생
  • ⇒ 이는 스택(stack)으로 해결 가능
# 스택으로 재귀 함수 구현하기(제귀를 제거)

from stack import Stack

def recur(n: int) -> int:
    """재귀를 제거한 recur() 함수"""
    s = Stack(n)

    while True:
        if n > 0:
            s.push(n)        # n값을 푸시
            n = n - 1
            continue
        if not s.is_empty(): #스택이 비어있지 않으면
            n = s.pop()      # 저장한 값을 n에 팝
            print(n)
            n = n - 2
            continue
        break

x = int(input('정숫값을 입력하세요: '))

recur(x)

 

📌 하노이의 탑

# hanoi.pyy
# 하노이탑 구현하기

def move(no: int, x: int, y: int) -> None:
    """원반 no개를 x기둥에서 y기둥으로 옮김"""
    if no > 1:
        move(no - 1, x, 6 - x - y)

    print(f'원반 [{no}]을(를) {x}기둥에서 {y}기둥으로 옮깁니다.')

    if no > 1:
        move(no - 1, 6 - x - y, y)

print('하노이의 탑을 구현합니다')
n = int(input('원반의 개수를 입력하세요: '))

move(n, 1, 3) # 1기둥에 쌓인 원반 n개를 3기둥으로 옮김
  • 기둥 번호를 정숫값 1, 2, 3으로 나타냄
  • 기둥 번호의 합이 6이므로 시작 기둥과 목표 기둥을 6-x-y로 구함

 

📌 8퀸 문제

📚 8퀸 문제 알아보기

  • 재귀 알고리즘을 설명할 때 자주 나오는 예제

8개의 퀸이 서로 공격하여 잡을 수 없도록 8 x 8 체스판에 배치할 것

 

📚 퀸 배치하기

체스판 전체 조합의 수는

64 * 63 * 62 * 61 * 60 * 59 * 58 * 57=178,462,987,637,760

 

1. 각 열에 퀸을 1개만 배치합니다.

=> 8 * 8 * 8 * 8 * 8 * 8 * 8 * 8 = 16,777,216

 

2. 각 행에 퀸을 1개만 배치합니다.

=> 262,144 가지의 조합 생략 가능

 

📚 분기 작업으로 문제 해결하기

  • 나누는 작업을 프로그램 용어로 “분기”라하고 함
  • 분기(branching) 작업
    • 차례대로 가지가 뻗어 나가듯이 배치 조합을 열거하는 방법
  • 분할 정복법(divide and conqure, 분할해결법)
    • 큰 문제를 작은 문제로 분할하고 작은 무넺 풀이법을 결합하여 전체 결합법을 얻는 방법
# 각 열에 퀸을 1개 배치하는 조합을 재귀적으로 나열하기

pos = [0] * 8

def put() -> None:
    """각 열에 배치한 퀸의 위치를 출력"""
    for i in range(8):
        print(f'{pos[i]:2}', end='')
    print()

def set(i: int) -> None:
    """i열에 퀸을 배치"""
    for j in range(8):
        pos[i] = j
        if i == 7:
            put()
        else:
            set(i+1)

set(0)
  • 배열 pos는 퀸의 배치를 표현
  • i열에 배치한 퀸의 위차가 j행에 있다면 pos[i]의 값은 j
  • set( )은 pos[i] 에 차례대로 값을 대입하는 재귀함수

 

📚 한정 작업과 분기 한정법

  • 한정(bounding) 작업
    • 필요하지 않은 분기를 없애서 불필요한 조합은 열거하지 않는 방법
  • 분기 한정법(branching and bounding method)
    • 분기 작업과 한정 작업을 조합하여 문제 풀이를 하는 방법
# 각 열에 퀸을 1개 배치하는 조합을 재귀적으로 나열하기

pos = [0] * 8
flag = [False] * 8

def put() -> None:
    """각 열에 배치한 퀸의 위치를 출력"""
    for i in range(8):
        print(f'{pos[i]:2}', end='')
    print()

def set(i: int) -> None:
    """i열에 퀸을 배치"""
    for j in range(8):
        if not flag[j]:
            pos[i] = j
            if i == 7:
                put()
            else:
                flag[j] = True
                set(i + 1)
                flag[j] = False

set(0)
  • flag라는 배열을 이용해 같은 행에 중복하여 퀸을 배치하지 않도록 함
  • 배치된 행에는 True가 되어 변경
  • 재귀 호출한 set(i + 1) 함수가 끝나면 퀸을 j행에서 제거해서 flag[j]에 아직 배치되지 않음을 나타내는 False를 대입

 

📚 8퀸 문제 해결 프로그램 만들기

  • 대각선 방향으로도 움직일 수 있으므로 체크 필요
  • flag_b는 ↗↙ 방향을 체크. flab_c는 ↖↘을 체크
# 각 열에 퀸을 1개 배치하는 조합을 재귀적으로 나열하기

pos = [0] * 8           # 각 열에 배치한 퀸의 위치
flag_a = [False] * 8    # 각 행에 퀸을 배치했는지 체크
flag_b = [False] * 15    # 대각선 방향(↗↙)으로 퀸을 배치했는지 체크
flag_c = [False] * 15   # 대각선 방향(↖↘)으로 퀸을 배치했는지 체크

def put() -> None:
    """각 열에 배치한 퀸의 위치를 출력"""
    for i in range(8):
        print(f'{pos[i]:2}', end='')
    print()

def set(i: int) -> None:
    """i열의 알맞은 위치에 퀸을 배치"""
    for j in range(8):
        if ( not flag_a[j]              # j행에 퀸이 배치 되지 않았다면
            and not flag_b[i + j]       # 대각선 방향(↗↙)으로 퀸을 배치 되지 않았다면
            and not flag_c[i - j + 7]): # 대각선 방향(↖↘)으로 퀸을 배치 되지 않았다면
            pos[i] = j                  # 퀸을 j행에 배치
            if i == 7:                  # 모든 열에 퀸 배치 완료
                put()
            else:
                flag_a[j] = flag_b[i + j] = flag_c[i - j + 7] = True
                set(i + 1)              # 다음 열에 퀸을 배치
                flag_a[j] = flag_b[i + j] = flag_c[i - j + 7] = False

set(0)
  • 퀸이 배치된 상태를 더 쉽게 알아보기 위해서는 다음의 함수를 사용하면 됩니다.
def put() -> None:
    """퀸의 배치를 ■와 □으로 출력"""
    for j in range(8):
        for i in range(8):
            print('■' if pos[i] == j else '□', end='')
    print()
반응형
profile

다라다라V

@DaraDaraV

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!