Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

프로그래밍 끄적끄적

카탈랑 수 본문

알고리즘

카탈랑 수

soeunkk 2022. 9. 26. 20:50

카탈랑 수(Catalan number)는 

1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, ... 로 이루어진 수열로,

조합론에서 빈번하게 나타나는 수열이다.

 

점화식으로 나타내면 다음과 같다.

$$ C_{0} = 1, \, C_{n+1} = \sum_{i=0}^{n}{C_{i}C_{n-i}} \; (n \geq 1) $$

 

이 점화식을 따르는 대표적인 예제로, 'n개의 괄호 쌍으로 나타낼 수 있는 올바른 괄호 조합 구하기'가 있다.

(이때 올바른 괄호란, '('의 개수가 항상 많거나 같다가 마지막엔 '('의 개수와 ')'의 개수가 같은 식을 말한다.)

 

점화식을 위해 C(0) = 1이라고 설정한다.

1개의 괄호 쌍

1개의 괄호 쌍은 '()'으로 1개다.

$$C_{1} = 1 $$

 

2개의 괄호 쌍

기본적으로 한 쌍의 괄호가 있다고 할 때, 괄호 1개를 더 추가하는 방법은 2가지다.

 

안에 0개, 밖에 1개의 괄호 추가: ()()
$$C_{0}C_{1} = {1} \times {1} = {1} $$


안에 1개, 밖에 0개의 괄호 추가: (())
$$C_{1}C_{0} = {1} \times {1} = {1} $$

 

위의 경우는 둘 중 하나만 일어나면 되므로 합의 법칙을 적용한다.

따라서 2개의 괄호를 표현하는 경우의 수는 다음과 같다.

$$C_{2} = C_{0}C_{1} + C_{1}C_{0} = {2} $$

 

3개의 괄호 쌍

기본적으로 한 쌍의 괄호 ()가 있다고 할 때, 괄호 2개를 더 추가하는 방법은 3가지다.

 

안에 0개, 밖에 2개의 괄호 추가: ()()() , ()(())
$$C_{0}C_{2} = {1} \times {2} = {2} $$


안에 1개, 밖에 1개의 괄호 추가: (())()
$$C_{1}C_{1} = {1} \times {1} = {1} $$


안에 2개, 밖에 0개의 괄호 추가: ((())) , (()())
$$C_{2}C_{0} = {2} \times {1} = {2} $$

 

따라서 3개의 괄호를 표현하는 경우의 수는 다음과 같다.

$$C_{3} = C_{0}C_{2} + C_{1}C_{1} + C_{2}C_{0} = {5} $$

 


일반화하면,

n+1개의 괄호 쌍 조합은 한 쌍의 괄호를 기준으로 안에 i개의 괄호 쌍과 바깥에 n-i개의 괄호 쌍을 추가하면 된다.

(이 때 i는 0부터 n까지다.)

$$ C_{n+1} = \sum_{i=0}^{n}{C_{i}C_{n-i}} $$

 

재귀함수로 카탈랑 수 점화식을 구현한 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static int catalan(int n) {
    int result = 0;
 
    if (n <= 1) {
        return 1;
    }
 
    for (int i = 0; i < n; i++) {
        result += catalan(i) * catalan(n - 1 - i);
    }
    
    return result;
}
 
cs

 

DP로 카탈랑 수 점화식을 구현한 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
public static int catalan(int n) {
    int[] c = new int[n + 1];
    c[0= 1;
 
    for (int x = 1; x <= n; x++) {
        for (int i = 0; i <= x - 1; i++) {
            c[x] += c[i] * c[x - 1 - i];
        }
    }
 
    return c[n];
}
cs

 

 

카탈랑 수는 조합론을 통해 간단히 일반식으로도 나타낼 수 있다.

 

$$ C_{n} = \frac{1}{n+1} {{2n} \choose {n}} = \frac{(2n)!}{(n+1)!\,n!} $$

 

참고로 이 식의 증명
(0,0)에서 (n,n)까지 격자점을 지나는 최단거리의 경로 중에서

직선 y=x를 넘지 않는 경로의 개수를 구하는 것으로 가능하다.

 

카탈랑 수 일반식을 구현한 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
public static int catalan(int n) {
    long result = 1;
 
    for (int i = n + 2; i <= 2 * n; i++) {
        result *= i;
    }
    for (int i = 1; i <= n; i++) {
        result /= i;
    }
 
    return (int)result;
}
cs

https://school.programmers.co.kr/learn/courses/30/lessons/12929

위 링크는 프로그래머스에서 올바른 괄호의 개수를 구하는 문제이다.

 

각 방법으로 풀었을 때 걸린 시간과 메모리는 다음과 같다.

재귀함수(점화식): 10.91ms, 102MB

DP(점화식): 0.02ms, 74.1MB

일반식: 0.03ms 73.5MB

 

점화식 DP는 시간복잡도가 O(n^2)이고 일반식은 시간 복잡도가 O(n)이라서

일반식이 훨씬 빠를 줄 알았는데 오히려 DP가 살짝 더 빨랐다.

점화식을 DP로 구현하거나 일반식을 구현하는 방법 중 취향에 맞게 선택하면 될 것 같다.

(나는 조금 더 직관적인 점화식 DP 버전을 사용할 것 같다.)

 

 

🔍 그럼 이 카탈랑 수는 어떤 문제에 적용 가능한가?

  • n개의 괄호 쌍의 수식 조합
        ((()))    (()())    (())()    ()()()    ()(())

  • 길이가 2n인 뒤크 단어(dyck word)의 개수
    뒤크 단어는 n개의 X와 n개의 Y로 이루어진 문자열 중 처음부터 X와 Y의 개수를 세었을 때 항상 X가 Y보다 많거나 같은 것을 가리킨다. 즉 괄호 쌍 조합은 X가 '(' 이고 Y가 ')' 인 특정 케이스라고 볼 수 있다.
        XXXYYY   XYXXYY    XYXYXY    XXYYXY    XXYXYY

  • leaf 노드가 n+1개인 서로 다른 포화 이진 트리의 개수 (= 자식을 가진 노드가 n개인 서로 다른 포화 이진 트리의 개수)

 

  • n+1개의 항에 괄호를 씌울 수 있는 조합 (= n+1개의 항에 이항 연산을 적용하는 순서의 모든 경우의 수)
        ((ab)c)d    (a(bc))d    (ab)(cd)    a((bc)d)    a(b(cd))

  • n+2각형을 n개의 삼각형으로 나누는 방법의 수

Comments