#점화식 #분할정복 #피보나치 수열
비효율적이나, 꼭 필요할 때가 있어서? → 분할 정복 알고리즘에 사용됨! → 이는 곧 merge sort의 작동 방식
f(n) = n * f(n-1)
이러한 식을 n에 대한 수식으로 표현할 수 있어야 한다는 것이다.▶ 알고리즘 중 ‘분할정복’ 기법에서는 시간복잡도 함수가 반드시 점화식의 형태로 나온다.
▶ (왜?) 재귀함수(호출)에 대한 시간복잡도는 반드시 점화식의 형태로 나오기 때문이다. 따라서, 점화식이 주어졌을 때 n에 대한 수식으로 바꾸는 것이 중요하다!
▶ 그래야지 시간복잡도를 구할 수 있다.
def fibo(n: int) -> int:
if n == 1 or n == 2:
return 1
else:
return fibo(n-1) + fibo(n-2)
→ 일반적이나, 비효율적 (함수 호출에 따른 오버헤드 + 매번 중복 계산)
▶ 효율적으로 하려면 동적 프로그래밍 기법으로 가야! / 아니면 반복문 활용 재귀로..
즉, 배열에 저장해서 필요할 때마다 꺼내 쓴다. (여기) https://richwind.co.kr/3
(피보나치 수열 풀이 방정식 업로드)
# 양의 정수 n의 팩토리얼 값을 재귀적으로 구함
def factorial(n: int) -> int:
if n >= 1:
return n * factorial(n-1)
else:
return 1
# 정숫값 x와 y의 최대 공약수를 반환
def gcd(x: int, y: int) -> int:
if y == 0:
return x
else:
return gcd(y, x % y)
# 원반 no개를 start 기둥에서 end 기둥으로 옮김
def move(no: int, start: int, end: int) -> None:
if no > 1:
move(no-1, start, 6-start-end)
print(f'원반 [{no}]을(를) {start}기둥에서 {end}기둥으로 옮깁니다.')
if no > 1:
move(no-1, 6-start-end, end)
우선, 문제에서 "뭘 해야 하는지"를 명확하게 명시하고 시작한다.
move(no, start, end) → 즉, no개의 원반을 start에서 end로 옮긴다.
그 다음, 전체적인 로직을 그려본다. (6-start-end
== mid
라 간주하겠다.)
1. move(no-1, start, mid)
2. move(1, start, end) → print(no, start, end)
3. move(no-1, mid, end)
재귀함수와 점화식/수학적 귀납법은 서로 연관되어 있다.
"p(1)이 참이라 가정할 때, p(n-1)이 참이면 p(n)도 참이다"라는 수학적 귀납법을 적용함에 있어서, 재귀함수도 마찬가지로 종료 조건이 반드시 필요하다!
여기서 종료조건은 n == 1
일 때라고 볼 수 있다.
대표적인 DFS과 backtracking 문제이다.
DFS(Depth First Search)란,
backtracking이란,
# 일반적인 백트래킹 알고리즘
class Node:
...
def checknode (v: Node) -> None:
u = Node()
if promising(v):
if (v에 해답이 있으면)
해답을 출력;
else:
for (v의 모든 자식 노드 u에 대해서)
checknode(u)
결국 8퀸(N퀸) 문제를 풀기 위해,
일반적인 백트래킹 알고리즘과 promising() 함수를 구현함으로써, 해당 노드의 유망성(즉, 정답이 있는지)를 파악하고 없다면 필요없는 자식 노드들은 가지치기 한다.
조금 스포하자면, 결과를 출력했을 때 결과값이 대칭을 이룬다는 사실 또한 알 수 있다.
방법1. 배열을 이용
# 8퀸 문제 알고리즘 구현하기 (배열 활용)
pos = [0] * 8 # 각 열에 배치한 퀸의 위치
flag_a = [False] * 8 # 각 행에 퀸을 배치했는지 체크
flag_b = [False] * 15 # 대각선(/) 방향으로 퀸을 배치했는지 체크
flag_c = [False] * 15 # 대각선(/ 반대) 방향으로 퀸을 배치했는지 체크
""" 함수 1) 각 열에 배치한 퀸의 위치를 출력 """
def put() -> None:
for i in range(8):
print(f'{pos[i]:2}', end=' ')
print()
""" 함수 2) i열의 알맞은 위치에 퀸을 배치 """
def set(i: int) -> None:
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
if j == 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
flag_a와 flag_b는 대각선의 인덱스로 0 ~ 2 * (n - 1)까지이다.
(4 x 4 이상부터 직접 그려보면 그 규칙을 찾을 수 있었다.)
방법2. 백트래킹 기본 알고리즘을 이용
이건 백준 풀이에 설명하겠다.