Featured image of post Числа Каталана: Почему скобки и структуры деревьев дают одинаковые количества

Числа Каталана: Почему скобки и структуры деревьев дают одинаковые количества

Объяснение чисел Каталана — удивительной последовательности, связывающей правильные скобочные последовательности, бинарные деревья и триангуляцию многоугольников. Мы также рассмотрим методы их вычисления с помощью рекурсии и динамического программирования.

1. Введение: Что такое числа Каталана?

В мире математики и информатики мы часто наблюдаем красивое явление, когда несколько, казалось бы, совершенно разных задач на самом деле имеют абсолютно одинаковую внутреннюю структуру. Одним из ярких примеров являются числа Каталана.

Названная в честь бельгийского математика Эжена Шарля Каталана, эта последовательность начинается так:

$$ C_0 = 1, \quad C_1 = 1, \quad C_2 = 2, \quad C_3 = 5, \quad C_4 = 14, \quad C_5 = 42, \quad C_6 = 132, \quad C_7 = 429, \quad \dots $$

Эта последовательность возникает как решение удивительно разнообразных комбинаторных задач. В этой статье мы представим четыре известных примера, связанных с числами Каталана (правильные скобочные последовательности, бинарные деревья, триангуляция многоугольника и пути Дика). Мы разберем их рекурсивную структуру, чтобы понять, почему все они сводятся к одной и той же последовательности. Кроме того, мы рассмотрим алгоритмы их вычисления с использованием динамического программирования (DP) и математический вывод через производящие функции.

2. Четыре конкретных примера чисел Каталана

Пример 1: Правильные скобочные последовательности

В программировании крайне важно убедиться, что скобки расставлены корректно. Количество «правильных скобочных последовательностей», которые можно составить, используя $n$ пар скобок (), в точности равно числу Каталана $C_n$.

Правильная скобочная последовательность — это такая строка, в которой при чтении слева направо количество закрывающих скобок ) ни в какой момент не превышает количество открывающих скобок (.

Давайте посмотрим на случай, когда $n = 3$. Существует 5 правильных способов расположить 3 пары скобок. Это идеально совпадает с $C_3 = 5$.

  graph TD
    Root["C_3 = 5"] --> P1["((()))"]
    Root --> P2["(()())"]
    Root --> P3["(())()"]
    Root --> P4["()(())"]
    Root --> P5["()()()"]

Пример 2: Структуры бинарных деревьев

Теперь рассмотрим бинарные деревья — хорошо известную структуру данных. Количество возможных форм бинарного дерева, имеющего $n$ внутренних узлов, также равно числу Каталана $C_n$.

Для $n = 3$ существует 5 различных форм бинарных деревьев. Они различаются тем, прикреплены ли узлы к левому или правому поддереву.

  graph TD
    T1["Tree 1"] --> N1_1["Node"]
    N1_1 --> N1_2["Left"]
    N1_2 --> N1_3["Left"]

    T2["Tree 2"] --> N2_1["Node"]
    N2_1 --> N2_2["Left"]
    N2_2 --> N2_3["Right"]

    T3["Tree 3"] --> N3_1["Node"]
    N3_1 --> N3_2["Left"]
    N3_1 --> N3_3["Right"]

    T4["Tree 4"] --> N4_1["Node"]
    N4_1 --> N4_2["Right"]
    N4_2 --> N4_3["Left"]

    T5["Tree 5"] --> N5_1["Node"]
    N5_1 --> N5_2["Right"]
    N5_2 --> N5_3["Right"]

Пример 3: Триангуляция многоугольника

Числа Каталана также встречаются в геометрии. Количество способов разбить выпуклый $(n+2)$-угольник на $n$ треугольников путем проведения непересекающихся диагоналей между вершинами в точности равно $C_n$.

Например, когда $n = 3$, мы рассматриваем способы триангуляции пятиугольника ($3+2=5$). Существует ровно 5 способов провести диагонали, чтобы образовать 3 треугольника. И снова мы видим число $C_3 = 5$.

Пример 4: Пути Дика

Числа Каталана также появляются в задачах о путях на сетке. На сетке размером $n \times n$ рассмотрим кратчайшие пути из левого нижнего угла $(0, 0)$ в правый верхний угол $(n, n)$, перемещаясь только вправо или вверх на одну единицу за шаг. Количество таких путей, которые никогда не пересекают диагональ $y = x$ (что означает, что они всегда удовлетворяют условию $y \le x$), равно $C_n$. Они называются путями Дика.

Если обозначить движение вправо как R, а вверх как U, то условие требует, чтобы в любом префиксе пути количество U никогда не превышало количество R. Это строго эквивалентно отношению между ( и ) в правильных скобочных последовательностях.

3. Почему они одинаковы? (Скрытая структура)

Почему все эти, казалось бы, не связанные между собой задачи приводят к одной и той же последовательности Каталана? Ответ кроется в том факте, что все они имеют абсолютно одинаковую рекурсивную структуру.

Число Каталана $C_n$ определяется следующим рекуррентным соотношением:

$$ C_0 = 1 $$$$ C_{n} = \sum_{i=0}^{n-1} C_i C_{n-1-i} \quad (n \ge 1) $$

Давайте интуитивно поймем, как выводится это рекуррентное соотношение, на примере «правильных скобок».

Рассмотрим произвольную правильную скобочную последовательность $S$ длины $2n$. $S$ должна начинаться с открывающей скобки (. Где-то в строке должна существовать ровно одна парная ей закрывающая скобка ). Сосредоточив внимание на этой конкретной паре, строку $S$ можно однозначно разбить на следующий вид:

$$ S = ( A ) B $$

Здесь $A$ и $B$ сами по себе являются правильными скобочными последовательностями (они могут быть и пустыми строками). Предположим, что подстрока $A$, которая находится между начальной ( и соответствующей ей ), содержит $i$ пар скобок $(0 \le i \le n-1)$. Поскольку во всей строке $n$ пар, а 1 пара занята внешними ( ), оставшаяся подстрока $B$ должна содержать $(n - 1 - i)$ пар.

  • Количество способов сформировать $A$ равно $C_i$
  • Количество способов сформировать $B$ равно $C_{n-1-i}$

Следовательно, для фиксированного значения $i$ количество возможных строк равно $C_i \times C_{n-1-i}$. Поскольку $i$ может принимать любое значение от $0$ до $n-1$, суммирование всех этих возможностей дает $C_n$. В этом и заключается смысл рекуррентного соотношения.

  graph LR
    S["S"] --> Split["( A ) B"]
    Split --> A["A : i пар"]
    Split --> B["B : n-1-i пар"]
    A --> CountA["C_i способов"]
    B --> CountB["C_{n-1-i} способов"]

Точно такое же разбиение работает и для «Бинарных деревьев». Если мы назначим один узел корнем и выделим $i$ узлов левому поддереву, то правое поддерево должно забрать оставшиеся $n-1-i$ узлов. Это дает идентичное рекуррентное соотношение.

4. Математический вывод явной формулы

Числа Каталана можно выразить очень простой явной формулой (Closed-form formula), используя комбинаторные обозначения:

$$ C_n = \frac{1}{n+1} \binom{2n}{n} = \frac{(2n)!}{(n+1)!n!} $$

Как выводится эта элегантная формула? Давайте рассмотрим два основных подхода.

4.1. Доказательство с помощью принципа отражения

Мы можем доказать эту формулу с помощью путей Дика. Общее количество кратчайших путей от $(0,0)$ до $(n,n)$ равно $\binom{2n}{n}$, потому что из $2n$ шагов всего мы должны выбрать $n$ шагов для движения вправо.

Из этого мы должны вычесть пути, нарушающие условие (то есть те, которые пересекают прямую $y = x$ и касаются прямой $y = x + 1$). Пусть $P$ — первая точка, в которой нарушающий условие путь касается $y = x + 1$. Мы отражаем часть пути от точки $P$ до конечной точки $(n,n)$ относительно прямой $y = x + 1$. Первоначальная конечная точка $(n,n)$ отражается в новую конечную точку $(n-1, n+1)$.

Удивительно, но существует идеальное взаимно-однозначное соответствие (биекция) между «неправильными путями из $(0,0)$ в $(n,n)$» и «ВСЕМИ путями из $(0,0)$ в $(n-1, n+1)$». Общее количество путей из $(0,0)$ в $(n-1, n+1)$ равно $\binom{2n}{n-1}$.

Следовательно, количество правильных путей равно:

$$ C_n = \binom{2n}{n} - \binom{2n}{n-1} $$

Мы можем алгебраически упростить это:

$$ C_n = \binom{2n}{n} - \frac{n}{n+1} \binom{2n}{n} = \left( 1 - \frac{n}{n+1} \right) \binom{2n}{n} = \frac{1}{n+1} \binom{2n}{n} $$

4.2. Подход через производящие функции

Пусть производящая функция для чисел Каталана $C(x) = \sum_{n=0}^\infty C_n x^n$. Используя рекуррентное соотношение $C_{n} = \sum_{i=0}^{n-1} C_i C_{n-1-i}$, мы находим, что производящая функция удовлетворяет следующему уравнению:

$$ C(x) = 1 + x [C(x)]^2 $$

Это можно рассматривать как квадратное уравнение относительно $C(x)$: $x [C(x)]^2 - C(x) + 1 = 0$. Применяя формулу корней квадратного уравнения, получаем:

$$ C(x) = \frac{1 \pm \sqrt{1 - 4x}}{2x} $$

Чтобы удовлетворить условию $C(0) = 1$ при $x \to 0$, мы должны выбрать знак минус.

$$ C(x) = \frac{1 - \sqrt{1 - 4x}}{2x} $$

Разлагая $\sqrt{1 - 4x} = (1 - 4x)^{1/2}$ с использованием обобщенного бинома Ньютона (ряд Тейлора) и приравнивая коэффициенты, мы приходим к $C_n = \frac{1}{n+1} \binom{2n}{n}$.

5. Алгоритмы вычисления чисел Каталана

При программном вычислении чисел Каталана используются в основном три подхода.

5.1. Простая рекурсия (Naive Recursion)

Это предполагает прямую реализацию рекуррентного соотношения. Однако из-за того, что одни и те же значения вычисляются многократно, временная сложность растет экспоненциально, что делает этот метод непригодным для больших значений $n$.

1
2
3
4
5
6
7
8
9
def catalan_recursive(n):
    # Базовый случай
    if n <= 1:
        return 1
    
    res = 0
    for i in range(n):
        res += catalan_recursive(i) * catalan_recursive(n - 1 - i)
    return res

5.2. Динамическое программирование

Используя мемоизацию (или динамическое программирование “снизу вверх”) для сохранения вычисленных результатов в массив, мы можем уменьшить временную сложность до $O(n^2)$.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def catalan_dp(n):
    # Инициализация DP-таблицы. C_0 = 1
    dp = [0] * (n + 1)
    dp[0] = 1
    
    # Вычисление на основе рекуррентного соотношения
    for i in range(1, n + 1):
        for j in range(i):
            dp[i] += dp[j] * dp[i - 1 - j]
            
    return dp[n]

# Тест
for i in range(7):
    print(f"C_{i} =", catalan_dp(i))

5.3. Явная формула

Используя формулу, мы можем вычислить значение с временной сложностью $O(n)$, просто выполняя вычисления факториалов.

1
2
3
4
5
6
7
8
9
import math

def catalan_formula(n):
    # C_n = (2n)! / ((n+1)! * n!)
    return math.comb(2 * n, n) // (n + 1)

# Тест
for i in range(7):
    print(f"C_{i} =", catalan_formula(i))

6. Заключение

Последовательность чисел Каталана $C_n$ — это захватывающая последовательность, которая единообразно проявляется во множестве, казалось бы, разных задач, таких как правильные скобочные последовательности, формы бинарных деревьев, триангуляция многоугольников и пути Дика. Причина, по которой эти задачи дают одинаковое количество, заключается в том, что все они воплощают общую рекурсивную структуру: «разбиение целого на две подзадачи и их объединение».

При изучении алгоритмов и структур данных понимание математической подоплеки развивает способность видеть суть проблемы. Это также служит отличным упражнением по динамическому программированию, поэтому обязательно попробуйте написать код и поэкспериментировать самостоятельно!

comments powered by Disqus