$n$번 행의 $k$번째 값 $a_{n, k}$에 대해 다음 점화식 성립:
$$a_{n, k} = a_{(n-1), (k-1)} + a_{(n-1), k}$$
<출처: 파스칼의 삼각형(위키피디아)>
$n$번 구슬이 선택되는 경우: 1부터 $n-1$번 까지의 구슬에서 $k-1$개의 구슬을 선택하는 방법의 수
$${n-1 \choose k-1}$$
$n$번 구슬이 선택되지 않는 경우: 1부터 $n-1$번 까지의 구슬에서 $k$개의 구슬을 선택하는 방법의 수
$${n-1 \choose k}$$
따라서 다음 성립:
$${n \choose k} = {n-1 \choose k-1} + {n-1 \choose k}$$
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
...
문제: 이항계수 계산
입력 파라미터: 음이 아닌 정수 $n$과 $k$, 단, $k \le n$.
반환값: ${n \choose k}$
def bin(n, k):
# 초기값
if k == 0 or k == n:
return 1
# 재귀
else:
return bin(n-1, k-1) + bin(n-1, k)
def fib(n):
if (n <= 1):
return n
else:
return fib(n-2) + fib(n-1)
bin
알고리즘의 복잡도¶bin(n-1, k-1)
과 bin(n-1, k)
는 둘 모두
서로 독립적으로 bin(n-2, k-1)
를 계산함.bin(n, k)
계산을 위해 필요한 bin()
함수 호출 횟수 (증명 생략):def fib2(n):
f = []
f.append(0)
if n > 0:
f.append(1)
for i in range(2, n+1):
fi = f[i-2] + f[i-1]
f.append(fi)
return f[n]
# n = 5, k = 3 인 경우 6x4 크기의 영행렬 생성하기
# 리스트 조건제시법 활용
[[0 for _ in range(3+1)] for _ in range(5+1)]
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
# 이분검색 동적계획법
def bin2(n, k):
# n x k 모양의 행렬 준비하기.
# 리스트 조건제시법 활용
B = [[0 for _ in range(k+1)] for _ in range(n+1)]
for i in range(n+1):
for j in range(min(i, k) + 1):
if j == 0 or j == i:
B[i][j] = 1
else:
B[i][j] = B[i-1][j-1] + B[i-1][j]
return B[n][k]
bin2(30, 14)
145422675
import time
start = time.time()
bin(30,14)
end = time.time()
print(end-start)
28.078381061553955
j
변수에 대한 for
반복문 실행횟수i = 0일 때: 1회
i = 1일 때: 2회
i = 2일 때: 3회
...
i = k-1일 때: k회
i = k일 때: k+1회
i = (k+1)일 때: k+1회
...
i = n일 때: k+1회
$v_1$에서 어떤 마디 $v_n$으로 가는 경로 중 나머지 모든 마디를 한 번씩 꼭 거쳐서 가는 경로들의 수는?
따라서 총 경로의 개수는 다음과 같음:
$$(n-2)\times(n-3)\times\cdots\times 1= (n-2)!$$
경우 1: $\{v_1, v_2,\dots, v_k\}$ 에 속한 마디들만을 통해서 $v_i$에서 $v_j$로 가는 최단경로가 $v_k$를 거쳐가지 않는 경우.
$$D^{(k)}[i][j] = D^{(k-1)}[i][j]$$
경우 2: $\{v_1, v_2,\dots, v_k\}$ 에 속한 마디들만을 통해서 $v_i$에서 $v_j$로 가는 최단경로가 $v_k$를 거쳐가는 경우.
$$D^{(k)}[i][j] = D^{(k-1)}[i][k] + D^{(k-1)}[k][j]$$
from copy import deepcopy
def floyd_warshall(W):
n = len(W)
# D^(0) 지정
# 주의: deepcopy를 사용하지 않으면 W에 혼란을 발생시킴
D = deepcopy(W)
# k가 0부터 (n-1)까지 이동하면서 D가 D^(1), ..., D^(n)을 차례대로 모방함.
# 즉, D를 업데이트하는 방식을 이용하여 최종적으로 D^(n) 생성
for k in range(0, n):
# 행렬의 인덱스는 0부터 (n-1)까지 이동
for i in range(0, n):
for j in range(0, n):
D[i][j] = min(D[i][j] , D[i][k]+ D[k][j])
# 최종 완성된 D 반환
return D
# 무한에 해당하는 기호 사용
from math import inf
# inf 는 두 마디 사이에 이음선이 없음을 의미함.
W = [[0, 1, inf, 1, 5],
[9, 0, 3, 2, inf],
[inf, inf, 0, 4, inf],
[inf, inf, 2, 0, 3],
[3, inf, inf, inf, 0]]
floyd_warshall(W)
[[0, 1, 3, 1, 4], [8, 0, 3, 2, 5], [10, 11, 0, 4, 7], [6, 7, 2, 0, 3], [3, 4, 6, 4, 0]]
from copy import deepcopy
def floyd_warshall2(W):
n = len(W)
# deepcopy를 사용하지 않으면 D에 혼란을 발생시킴
D = deepcopy(W)
P = deepcopy(W)
# P 행렬 초기화. 모든 항목을 -1로 설정
for i in range(n):
for j in range(n):
P[i][j] = -1
# k가 0부터 (n-1)까지 이동하면서 D가 D^(1), ..., D^(n)을 차례대로 모방함.
# 그와 함께 동시에 P 행렬도 차례대로 업데이트함.
for k in range(0, n):
for i in range(0, n):
for j in range(0, n):
if D[i][k]+ D[k][j] < D[i][j]:
P[i][j] = k
D[i][j] = D[i][k]+ D[k][j]
# 최종 완성된 P도 반환
return D, P
path
함수는 두 마디 사이의 최단 경로상에 위치한 마디를 순서대로 보여줌.def path(P, q, r):
# 인덱스가 0부터 출발하기에 -1 또는 +1을 적절히 조절해야 함.
if P[q-1][r-1] != -1:
v = P[q-1][r-1]
path(P, q, v+1)
print(v+1,end=' ')
path(P, v+1, r)
_, P = floyd_warshall2(W)
P
[[-1, -1, 3, -1, 3], [4, -1, -1, -1, 3], [4, 4, -1, -1, 3], [4, 4, -1, -1, -1], [-1, 0, 3, 0, -1]]
path(P, 5, 3)
1 4
def path2(P, q, r, route):
# 인덱스가 0부터 출발하기에 -1 또는 +1을 적절히 조절해야 함.
if P[q-1][r-1] != -1:
v = P[q-1][r-1]
path2(P, q, v+1, route)
route.append(v+1)
path2(P, v+1, r, route)
return route
path2(P, 5, 3, [])
[1, 4]
def print_path2(P, i, j):
route = path2(P, i, j, [])
route.insert(0, i)
route.append(j)
print(" -> ".join([str(v) for v in route]))
print_path2(P, 5, 3)
5 -> 1 -> 4 -> 3
print_path2(P, 2, 5)
2 -> 4 -> 5
from itertools import product
def floyd_warshall3(W):
n = len(W)
D = deepcopy(W)
P = [[0] * n for i in range(n)]
for i, j in product(range(n), repeat=2):
if 0 < W[i][j] < inf:
P[i][j] = j
for k, i, j in product(range(n), repeat=3):
sum_ik_kj = D[i][k] + D[k][j]
if D[i][j] > sum_ik_kj:
D[i][j] = sum_ik_kj
P[i][j] = P[i][k]
return D, P
def path3(D, P, i, j):
# 인덱스가 0부터 출발하기에 -1 또는 +1을 적절히 조절해야 함.
path = [i-1]
while path[-1] != j-1:
path.append(P[path[-1]][j-1])
route = ' → '.join(str(p + 1) for p in path)
print(f"최단길이:{D[i-1][j-1]:>2}, 최단경로: {route}")
D, P = floyd_warshall3(W)
path3(D, P, 5, 3)
최단길이: 6, 최단경로: 5 → 1 → 4 → 3
path3(D, P, 2, 5)
최단길이: 5, 최단경로: 2 → 4 → 5