이항계수란 주어진 집합에서 원하는 개수만큼 순서없이 뽑는 조합의 개수를 의미한다. 여기서 이항
이란 한 개의 아이템에 대해서 뽑거나
뽑지않거나
두가지의 선택이 있기 때문에 붙은 단어이다. 이항계수의 정의는 다음과 같이 표현된다.
가장 단순하게 이항계수의 정의를 이용하여 구현해보자.
import math
def bino_coef_factorial(n, k):
return math.factorial(n-k) // math.factorial(k)
python의 팩토리얼 함수를 이용하여 간단하게 풀었다. 물론 함수를 이용하지 않고도 팩토리얼을 직접 구현해도 된다.
이항계수의 성질을 이용하여 동적 계획법으로 구현할수도 있다. 여기서 동적계획법이란 한 문제를 작은 단위의 문제로 쪼개어 푸는 방법이다.
위에서 살펴본 이항계수의 성질 3번에 따르면 이항계수는 그보다 작은 두 개의 부분식으로 쪼갤 수 있고, 쪼개진 부분식들은 그보다 작은 식으로 계속하여 쪼개질 수 있다.
또한 성질 2번에 의해서 전체에서 아무것도 선택하지 않는 조합 개수와 모든 아이템을 선택했을 때의 조합 개수는 모두 1이라는 것을 도출해볼 수 있는데, 이 말을 즉슨 부분식들을 쪼개다가 k가 0이 되거나 n과 k가 같아지는 순간은 무조건 1이라는 것이다. 이것을 이용하여 다음과 같이 재귀함수로 이항계수를 구할 수 있다.
def bino_coef(n, k):
if k == 0 or n == k:
return 1
return bino_coef(n-1, k) + bino_coef(n-1, k-1)
bino_coef(4, 2) # 6
이 구현법의 문제점은 부분식의 중복이다. 위 코드에서는 작은 인자를 넘겨가며 재귀적으로 함수를 계속 호출하는데, 결과 값을 얻기위해 같은 부분함수가 여러번 실행되어 심각한 성능 저하를 일으킨다.
이런 문제를 보완하기 위해, 한번 계산한 값은 캐쉬에 저장해가며 풀어보도록 하자.
def bino_coef(n, r):
# 1.
cache = [[0 for _ in range(r+1)] for _ in range(n+1)]
# 2.
for i in range(n+1):
cache[i][0] = 1
for i in range(r+1):
cache[i][i] = 1
# 3.
for i in range(1, n+1):
for j in range(1, r+1):
cache[i][j] = cache[i-1][j] + cache[i-1][j-1]
return cache[n][r]
(n+1)(k+1)
크기의 2차원 배열로 캐시를 초기화한다.이번에 살펴볼 방법은 보다 확장성이 높은 구현 방법이다. 이 구현법을 잘 익혀두면 확률을 구할때 등에도 활용할 수 있다.
이번에는 n번의 뽑을 기회 중에서 각 아이템을 뽑거나, 뽑지 않는 선택을 모두 계산하여 최종적으로 k개가 되었을때의 경우만 세보는 것이다.
def bino_coef_prob(n, k):
if k > n:
return 0
# 1.
cache = [[-1 for _ in range(n+1)] for _ in range(n+1)]
# 2.
def choose(times, got):
# 3.
if times == n:
return got >= k
# 4.
if cache[times][got] != -1:
return cache[times][got]
# 5.
cache[times][got] = 0.5 * choose(times+1, got) + 0.5 * choose(times+1, got+1)
return cache[times][got]
# 6.
return choose(0, 0)
>>> bino_coef_prob(10, 8)
이 구현 방식을 이용하면 확률을 구하는 함수로도 활용할 수 있다. 예를 들어 동전 n번을 던질 때 앞면이 k개가 나올 확률을 구한다고 할 때, 아래처럼 원식의 5번 부분을 살짝만 변형해주면 된다.
cache[times][got] = 0.5 * choose(times+1, got) + 0.5 * choose(times+1, got+1) # 5.
동전 던지는 예제라고 한다면 동전을 던져서 앞이 나오는 확률, 뒤가 나오는 확률 모두 0.5이다. 그래서 각 경우의 수에 단위확률을 곱해줌으로써 두 기대값을 구하고 더해줌으로써 확률을 구할 수 있다.
이항계수가 무엇이고 성질은 어떤게 있는지, 이를 이용해 어떻게 구현할 수 있는지 정리해보았다.3번의 식은 활용도가 높은 만큼 외우다시피 완벽하게 이해하는 것이 도움될 것 같다.
참고
https://shoark7.github.io/programming/algorithm/3-ways-to-get-binomial-coefficients
좋은 글 감사합니다