First version.

This commit is contained in:
krahets
2026-01-20 15:08:42 +08:00
parent 2213a59ff6
commit 8071daddaa
106 changed files with 11790 additions and 0 deletions
@@ -0,0 +1,49 @@
# Стратегия поиска «разделяй и властвуй»
Мы уже знаем, что алгоритмы поиска делятся на две большие категории.
- **Полный перебор**: реализуется путем обхода структуры данных, временная сложность составляет *O*(*n*).
- **Адаптивный поиск**: использует особую организацию данных или априорную информацию, временная сложность может достигать *O*(log *n*) или даже *O*(1).
На практике **алгоритмы поиска с временной сложностью** *O*(log *n*) **обычно реализуются на основе стратегии «разделяй и властвуй»**, например двоичный поиск и деревья.
- **Двоичный поиск** на каждом шаге разбивает задачу (поиск целевого элемента в массиве) на более мелкую задачу (поиск целевого элемента в половине массива). Этот процесс продолжается до тех пор, пока массив не станет пустым или не будет найден целевой элемент.
- **Деревья** являются представителями стратегии «разделяй и властвуй». В структурах данных, таких как двоичное дерево поиска, АВЛ-дерево, куча и др., временная сложность различных операций составляет *O*(log *n*).
Стратегия «разделяй и властвуй» для двоичного поиска выглядит следующим образом.
- **Задачу можно разбить**: двоичный поиск рекурсивно разбивает исходную задачу (поиск в массиве) на подзадачи (поиск в половине массива), что достигается сравнением среднего элемента с целевым элементом.
- **Подзадачи независимы**: в двоичном поиске на каждом этапе обрабатывается только одна подзадача, которая не зависит от других подзадач.
- **Решения подзадач не требуют объединения**: двоичный поиск направлен на поиск конкретного элемента, поэтому объединять решения подзадач не требуется. Когда подзадача решена, исходная задача также считается решенной.
Стратегия «разделяй и властвуй» повышает эффективность поиска, поскольку при грубом поиске на каждом этапе можно исключить только один вариант, **тогда как при поиске «разделяй и властвуй» на каждом этапе можно исключить половину вариантов**.
### Реализация двоичного поиска на основе стратегии «разделяй и властвуй»
В предыдущих главах двоичный поиск был реализован на основе итераций. Теперь мы реализуем его на основе принципа «разделяй и властвуй» (рекурсии).
!!! question
Дан упорядоченный массив `nums` длиной $n$, в котором все элементы уникальны. Необходимо найти элемент `target`.
Для применения стратегии «разделяй и властвуй» обозначим подзадачу для поискового интервала $[i, j]$ как $f(i, j)$.
Начав с исходной задачи $f(0, n - 1)$, выполняем двоичный поиск по следующему алгоритму:
1. Вычисление средней точки $m$ поискового интервала $[i, j]$ и исключение половины интервала на основе сравнения со средним элементом.
2. Рекурсивное решение подзадачи с уменьшенным вдвое размером, возможны варианты $f(i, m - 1)$ и $f(m + 1, j)$.
3. Повторение шагов `1.` и `2.` до тех пор, пока не будет найден элемент `target` или интервал не станет пустым.
На рис. 12.4 иллюстрируется процесс применения стратегии «разделяй и властвуй» при двоичном поиске элемента 6 в массиве.
![Стратегия «разделяй и властвуй» в двоичном поиске](../assets/media/image725.jpeg)
В коде реализации объявляется рекурсивная функция `dfs()` для решения задачи $f(i, j)$:
```src
[file]{binary_search_recur}-[class]{}-[func]{binary_search}
```
@@ -0,0 +1,95 @@
# Задача построения бинарного дерева
!!! question
Даны прямой обход `preorder` и центральный обход `inorder` бинарного дерева. Необходимо построить бинарное дерево и вернуть корневой узел. Предполагается, что в бинарном дереве нет узлов с повторяющимися значениями (как показано на рисунке ниже).
![Пример данных для построения бинарного дерева](../assets/build_tree_example.png)
### Определение задачи для метода «разделяй и властвуй»
Исходная задача определяется как построение бинарного дерева из `preorder` и `inorder`, что является типичной задачей для метода «разделяй и властвуй».
- **Задачу можно разбить**: с точки зрения стратегии «разделяй и властвуй», мы можем разделить исходную задачу на две подзадачи: построение левого поддерева и построение правого поддерева, плюс одна операция: инициализация корневого узла. Для каждого поддерева (подзадачи) мы можем повторно использовать тот же метод разбиения, разделяя его на более мелкие поддеревья (подзадачи), пока не достигнем наименьшей подзадачи (пустое поддерево).
- **Подзадачи независимы**: левое и правое поддеревья независимы друг от друга, между ними нет пересечений. При построении левого поддерева нам нужно обращать внимание только на части центрального и прямого обходов, соответствующие левому поддереву. То же самое для правого поддерева.
- **Решения подзадач можно объединить**: как только мы получим левое и правое поддеревья (решения подзадач), мы можем связать их с корневым узлом и получить решение исходной задачи.
### Как разделить поддеревья
Согласно приведенному выше анализу, эту задачу можно решить с помощью стратегии «разделяй и властвуй», **но как разделить левое и правое поддеревья с помощью прямого обхода `preorder` и центрального обхода `inorder`**?
По определению, `preorder` и `inorder` можно разделить на три части.
- Прямой обход: `[ Корневой узел | Левое поддерево | Правое поддерево ]`, например, для дерева на рисунке выше это `[ 3 | 9 | 2 1 7 ]`.
- Центральный обход: `[ Левое поддерево | Корневой узел | Правое поддерево ]`, например, для дерева на рисунке выше это `[ 9 | 3 | 1 2 7 ]`.
На примере данных с рисунка выше мы можем получить результат разбиения, выполнив шаги, показанные на следующем рисунке.
1. Первый элемент прямого обхода 3 — это значение корневого узла.
2. Найдем индекс корневого узла 3 в `inorder`, используя этот индекс, можно разделить `inorder` на `[ 9 | 3 | 1 2 7 ]`.
3. На основе результата разбиения `inorder` легко получить, что количество узлов в левом и правом поддеревьях равно 1 и 3 соответственно, таким образом можно разделить `preorder` на `[ 3 | 9 | 2 1 7 ]`.
![Разделение поддеревьев в прямом и центральном обходах](../assets/build_tree_preorder_inorder_division.png)
### Описание интервалов поддеревьев с помощью переменных
Согласно приведенному выше методу разбиения, **мы уже получили индексные интервалы корневого узла, левого поддерева и правого поддерева в `preorder` и `inorder`**. Для описания этих индексных интервалов нам нужно использовать несколько указательных переменных.
- Обозначим индекс корневого узла текущего дерева в `preorder` как $i$.
- Обозначим индекс корневого узла текущего дерева в `inorder` как $m$.
- Обозначим индексный интервал текущего дерева в `inorder` как $[l, r]$.
Как показано в таблице ниже, с помощью этих переменных можно представить индекс корневого узла в `preorder` и индексные интервалы поддеревьев в `inorder`.
<p align="center"> Таблица <id> &nbsp; Индексы корневого узла и поддеревьев в прямом и центральном обходах </p>
| | Индекс корневого узла в `preorder` | Индексный интервал поддерева в `inorder` |
| -------------- | ---------------------------------- | ---------------------------------------- |
| Текущее дерево | $i$ | $[l, r]$ |
| Левое поддерево| $i + 1$ | $[l, m-1]$ |
| Правое поддерево| $i + 1 + (m - l)$ | $[m+1, r]$ |
Обратите внимание, что $(m-l)$ в индексе корневого узла правого поддерева означает "количество узлов в левом поддереве", рекомендуется понять это с помощью следующего рисунка.
![Представление индексных интервалов корневого узла и левого и правого поддеревьев](../assets/build_tree_division_pointers.png)
### Реализация кода
Для повышения эффективности поиска $m$ мы используем хеш-таблицу `hmap` для хранения соответствия между элементами массива `inorder` и их индексами:
```src
[file]{build_tree}-[class]{}-[func]{build_tree}
```
На следующем рисунке показан рекурсивный процесс построения бинарного дерева. Каждый узел создается в процессе "спуска" рекурсии, а каждое ребро (ссылка) устанавливается в процессе "подъема".
=== "<1>"
![Рекурсивный процесс построения бинарного дерева](../assets/built_tree_step1.png)
=== "<2>"
![built_tree_step2](../assets/built_tree_step2.png)
=== "<3>"
![built_tree_step3](../assets/built_tree_step3.png)
=== "<4>"
![built_tree_step4](../assets/built_tree_step4.png)
=== "<5>"
![built_tree_step5](../assets/built_tree_step5.png)
=== "<6>"
![built_tree_step6](../assets/built_tree_step6.png)
=== "<7>"
![built_tree_step7](../assets/built_tree_step7.png)
=== "<8>"
![built_tree_step8](../assets/built_tree_step8.png)
=== "<9>"
![built_tree_step9](../assets/built_tree_step9.png)
Результаты разбиения прямого обхода `preorder` и центрального обхода `inorder` в каждой рекурсивной функции показаны на следующем рисунке.
![Результаты разбиения в каждой рекурсивной функции](../assets/built_tr
@@ -0,0 +1,91 @@
# Разделяй и властвуй
<u>Разделяй и властвуй (divide and conquer)</u> -- это важная и распространенная стратегия в алгоритмах. Обычно она реализуется с помощью рекурсии и включает два этапа: разделение и объединение.
1. **Разделение (этап разбиения)**: рекурсивное разбиение исходной задачи на две или более подзадачи до тех пор, пока не будет достигнута наименьшая подзадача.
2. **Объединение (этап слияния)**: начиная с решения наименьших подзадач, снизу вверх объединяются решения всех других подзадач, чтобы построить решение исходной задачи.
Сортировка слиянием является типичным примером применения стратегии «разделяй и властвуй» (см. рис. 12.1).
1. **Разделение**: рекурсивное разбиение исходного массива (исходной задачи) на два подмассива (подзадачи) до тех пор, пока в подмассивах не останется по одному элементу (наименьшая подзадача).
2. **Объединение**: снизу вверх объединяются упорядоченные подмассивы (решения подзадач), чтобы получить упорядоченный исходный массив (решение исходной задачи).
![Стратегия «разделяй и властвуй» в сортировке слиянием](../assets/media/image719.jpeg)
## Определение задачи для метода «разделяй и властвуй»
Чтобы определить, подходит ли задача для решения методом «разделяй и властвуй», можно использовать следующие критерии:
1. **Задачу можно разбить**: исходную задачу можно разбить на более мелкие, аналогичные подзадачи, которые можно рекурсивно разделить аналогичным образом.
2. **Подзадачи независимы**: подзадачи не пересекаются, не зависят друг от друга и могут быть решены независимо.
3. **Решения подзадач можно объединить**: решение исходной задачи получается путем объединения решений подзадач.
Очевидно, что сортировка слиянием соответствует этим трем критериям.
1. **Задачу можно разбить**: рекурсивное разбиение массива (исходной задачи) на два подмассива (подзадачи).
2. **Подзадачи независимы**: каждый подмассив можно отсортировать независимо (подзадачи можно решить независимо).
3. **Решения подзадач можно объединить**: два упорядоченных подмассива (решения подзадач) можно объединить в один упорядоченный массив (решение исходной задачи).
## Повышение эффективности с помощью стратегии «разделяй и властвуй»
**Стратегия «разделяй и властвуй» позволяет не только эффективно решать алгоритмические задачи, но и повышать эффективность алгоритмов**. Алгоритмы быстрой сортировки, сортировки слиянием и пирамидальной сортировки быстрее, чем сортировка выбором, пузырьком и вставками, именно благодаря применению стратегии «разделяй и властвуй».
Возникает вопрос: **почему метод «разделяй и властвуй» повышает эффективность алгоритма, в чем его основная логика**? Иными словами, почему разбиение большой задачи на несколько подзадач, решение этих подзадач и объединение их решений в решение исходной задачи оказывается более эффективным, чем непосредственное решение исходной задачи? Этот вопрос можно обсудить с точки зрения количества операций и параллельных вычислений.
### Оптимизация количества операций
Возьмем, к примеру, сортировку пузырьком, которая требует времени $O(n^2)$ для обработки массива длиной $n$. Предположим, что мы разделили массив на два подмассива, как показано на рис. 12.2. Тогда разбиение потребует времени $O(n)$, сортировка каждого подмассива -- $O((n/2)^2)$, а объединение двух подмассивов -- $O(n)$. Общая временная составит:
$$
O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n)
$$
![Сортировка пузырьком до и после разбиения массива](../assets/media/image721.jpeg)
Далее, решим следующее неравенство, в котором левая и правая части представляют общее количество операций до и после разбиения соответственно:
$$
\begin{aligned}
n^2 & > \frac{n^2}{2} + 2n \\
n^2 - \frac{n^2}{2} - 2n & > 0 \\
n(n - 4) & > 0
\end{aligned}
$$
**Это означает, что при $n > 4$ количество операций после разбиения меньше, и эффективность сортировки должна быть выше**. Обратите внимание, что временная сложность после разбиения остается квадратичной $O(n^2)$, но постоянный коэффициент в сложности уменьшается.
**Если продолжить разбиение подмассивов пополам, пока в них не останется по одному элементу**, то получится сортировка слиянием, временная сложность которой составляет $O(n \log n)$.
А что, **если мы установим несколько дополнительных точек разделения** и равномерно разделим исходный массив на $k$ подмассивов? Эта ситуация очень похожа на блочную сортировку, которая хорошо подходит для сортировки очень больших объемов данных, и теоретически ее временная сложность может достигать $O(n + k)$.
### Оптимизация параллельных вычислений
Известно, что подзадачи, созданные методом «разделяй и властвуй», независимы друг от друга, **поэтому их обычно можно решать параллельно**. Таким образом, этот метод не только снижает временную сложность алгоритма, **но и способствует параллельной оптимизации операционной системы**.
Параллельная оптимизация особенно эффективна в многоядерной или многопроцессорной среде, поскольку система может одновременно обрабатывать несколько подзадач, более полно используя вычислительные ресурсы, что значительно сокращает общее время выполнения.
Например, в блочной сортировке, изображенной на рис. 12.3, огромный объем данных равномерно распределяется по блокам. Задачи сортировки всех блоков можно распределить по вычислительным единицам, а затем объединить результаты.
![Параллельные вычисления в блочной сортировке](../assets/media/image723.jpeg)
## Типичные сценарии применения стратегии «разделяй и властвуй»
С одной стороны, стратегию «разделяй и властвуй» можно использовать для решения многих классических алгоритмических задач.
- **Поиск ближайшей пары точек**: этот алгоритм сначала делит множество точек на две части, затем находит ближайшую пару точек в каждой части, а затем находит ближайшую пару точек, охватывающую обе части.
- **Умножение больших чисел**: например, алгоритм Карацубы, который разлагает умножение больших чисел на несколько операций умножения и сложения меньших чисел.
- **Умножение матриц**: например, алгоритм Штрассена, который разлагает умножение больших матриц на несколько операций умножения и сложения матриц меньшего размера.
- **Задача о Ханойских башнях**: эту задачу можно решить с помощью рекурсии, что является типичным применением стратегии «разделяй и властвуй».
- **Задача о количестве инверсий**: если в последовательности предыдущее число больше последующего, то эти два числа образуют инверсию. Задачу о количестве инверсий можно решить с помощью подхода «разделяй и властвуй» и сортировки слиянием.
С другой стороны, стратегия «разделяй и властвуй» широко применяется в разработке алгоритмов и структур данных.
- **Двоичный поиск**: такой поиск делит отсортированный массив на две части по индексу среднего элемента. Затем, в зависимости от результата сравнения целевого значения со средним элементом, решает, какую половину исключить, и выполняет ту же операцию на оставшейся части.
- **Сортировка слиянием**: уже была рассмотрена в начале этого раздела, не будем еще раз повторяться.
- **Быстрая сортировка**: эта сортировка выбирает опорное значение, затем делит массив на два подмассива, элементы одного из которых меньше опорного значения, а элементы другого -- больше. Затем выполняет ту же операцию с обеими частями, пока в подмассиве не останется один элемент.
- **Блочная сортировка**: основная идея этой сортировки заключается в распределении данных по нескольким блокам и сортировке элементов в каждом из них. Затем происходит последовательное извлечение элементов из каждого блока для построения отсортированного массива.
- **Деревья**: например, двоичные деревья поиска, АВЛ-дерево, красно-черное дерево, B-дерево, дерево B+ и т. д. Операции поиска, вставки и удаления в них можно рассматривать как применение стратегии «разделяй и властвуй».
- **Кучи**: куча -- это особый вид полного двоичного дерева, и такие операции, как вставка, удаление и упорядочивание, фактически подразумевают использование метода «разделяй и властвуй».
- **Хеш-таблицы**: хотя хеш-таблицы напрямую не применяют подход «разделяй и властвуй», некоторые решения для разрешения коллизий в хешировании косвенно используют эту стратегию. Например, длинные цепочки в методе цепной адресации преобразуются в красно-черные деревья для повышения эффективности поиска.
Можно сказать, **что стратегия «разделяй и властвуй» -- это своего рода «скрытая» алгоритмическая идея**, присутствующая в различных алгоритмах и структурах данных.
@@ -0,0 +1,97 @@
# Задача о Ханойских башнях
В сортировке слиянием и построении двоичного дерева мы разбиваем исходную задачу на две подзадачи, размер которых составляет половину исходной задачи. Однако для задачи о Ханойских башнях мы используем другую стратегию разбиения.
!!! question
Даны три стержня, обозначенные как `A`, `B` и `C`. В начальном состоянии на стержне `A` находится $n$ дисков, расположенных сверху вниз в порядке возрастания размера. Наша задача -- переместить эти $n$ дисков на стержень `C`, сохранив их исходный порядок (как показано на рисунке ниже). При перемещении дисков необходимо соблюдать следующие правила.
1. Диск можно снять только с верхушки одного стержня и поместить на верхушку другого стержня.
2. За один раз можно переместить только один диск.
3. Меньший диск всегда должен находиться над большим диском.
![Пример задачи о Ханойских башнях](../assets/hanota_example.png)
**Обозначим задачу о Ханойских башнях размера $i$ как $f(i)$**. Например, $f(3)$ представляет задачу о Ханойских башнях по перемещению $3$ дисков с `A` на `C`.
### Рассмотрение базового случая
Как показано на рисунке ниже, для задачи $f(1)$, то есть когда имеется только один диск, мы можем просто переместить его с `A` на `C`.
=== "<1>"
![Решение задачи размера 1](../assets/hanota_f1_step1.png)
=== "<2>"
![hanota_f1_step2](../assets/hanota_f1_step2.png)
Как показано на рисунке ниже, для задачи $f(2)$, то есть когда имеется два диска, **поскольку необходимо всегда соблюдать условие, что меньший диск находится над большим, нужно использовать `B` для завершения перемещения**.
1. Сначала переместить верхний меньший диск с `A` на `B`.
2. Затем переместить больший диск с `A` на `C`.
3. Наконец, переместить меньший диск с `B` на `C`.
=== "<1>"
![Решение задачи размера 2](../assets/hanota_f2_step1.png)
=== "<2>"
![hanota_f2_step2](../assets/hanota_f2_step2.png)
=== "<3>"
![hanota_f2_step3](../assets/hanota_f2_step3.png)
=== "<4>"
![hanota_f2_step4](../assets/hanota_f2_step4.png)
Процесс решения задачи $f(2)$ можно обобщить следующим образом: **переместить два диска с `A` на `C`, используя `B`**. При этом `C` называется целевым стержнем, а `B` -- буферным стержнем.
### Разбиение на подзадачи
Для задачи $f(3)$, то есть когда имеется три диска, ситуация становится немного сложнее.
Поскольку решения $f(1)$ и $f(2)$ уже известны, мы можем мыслить с точки зрения стратегии «разделяй и властвуй», **рассматривая два верхних диска на `A` как единое целое**, и выполнить шаги, показанные на рисунке ниже. Таким образом, три диска успешно перемещаются с `A` на `C`.
1. Сделать `B` целевым стержнем, а `C` -- буферным стержнем, и переместить два диска с `A` на `B`.
2. Переместить оставшийся один диск с `A` непосредственно на `C`.
3. Сделать `C` целевым стержнем, а `A` -- буферным стержнем, и переместить два диска с `B` на `C`.
=== "<1>"
![Решение задачи размера 3](../assets/hanota_f3_step1.png)
=== "<2>"
![hanota_f3_step2](../assets/hanota_f3_step2.png)
=== "<3>"
![hanota_f3_step3](../assets/hanota_f3_step3.png)
=== "<4>"
![hanota_f3_step4](../assets/hanota_f3_step4.png)
По сути, **мы разбиваем задачу $f(3)$ на две подзадачи $f(2)$ и одну подзадачу $f(1)$**. После последовательного решения этих трех подзадач исходная задача также решается. Это показывает, что подзадачи независимы, и их решения можно объединить.
Таким образом, мы можем обобщить стратегию «разделяй и властвуй» для решения задачи о Ханойских башнях, показанную на рисунке ниже: разбить исходную задачу $f(n)$ на две подзадачи $f(n-1)$ и одну подзадачу $f(1)$, и решить эти три подзадачи в следующем порядке.
1. Переместить $n-1$ дисков с `A` на `B`, используя `C`.
2. Переместить оставшийся $1$ диск непосредственно с `A` на `C`.
3. Переместить $n-1$ дисков с `B` на `C`, используя `A`.
Для этих двух подзадач $f(n-1)$ **можно применить рекурсивное разбиение тем же способом**, пока не будет достигнута наименьшая подзадача $f(1)$. А решение $f(1)$ известно -- требуется только одна операция перемещения.
![Стратегия «разделяй и властвуй» для решения задачи о Ханойских башнях](../assets/hanota_divide_and_conquer.png)
### Реализация кода
В коде мы объявляем рекурсивную функцию `dfs(i, src, buf, tar)`, которая перемещает $i$ дисков с верхушки стержня `src` на целевой стержень `tar`, используя буферный стержень `buf`:
```src
[file]{hanota}-[class]{}-[func]{solve_hanota}
```
Как показано на рисунке ниже, задача о Ханойских башнях формирует рекурсивное дерево высотой $n$, где каждый узел представляет подзадачу, соответствующую вызову функции `dfs()`, **поэтому временная сложность составляет $O(2^n)$, а пространственная сложность -- $O(n)$**.
![Рекурсивное дерево задачи о Ханойских башнях](../assets/hanota_recursive_tree.png)
!!! quote
Задача о Ханойских башнях происходит из древней легенды. В храме в древней Индии у монахов было три высоких алмазных стержня и $64$ золотых диска разного размера. Монахи постоянно перемещали диски, веря, что в тот момент, когда последний диск будет правильно размещен, этот мир закончится.
Однако, даже если монахи будут перемещать диски каждую секунду, потребуется примерно $2^{64} \approx 1.84×10^{19}$ секунд, что составляет около $5850$ миллиардов лет, что намного превышает текущую оценку возраста Вселенной. Поэтому, если эта легенда правдива, нам не стоит беспокоиться о конце света.
@@ -0,0 +1,9 @@
# Разделяй и властвуй
![Разделяй и властвуй](../assets/covers/chapter_divide_and_conquer.jpg)
!!! abstract
Сложная задача разбивается на уровни, и каждое разбиение делает её проще.
Разделяй и властвуй раскрывает важный факт: начиная с простого, всё перестаёт быть сложным.
@@ -0,0 +1,13 @@
# Резюме
### Ключевые моменты
- «Разделяй и властвуй» — это распространенная стратегия проектирования алгоритмов, которая включает два этапа: разделение и объединение, и обычно реализуется на основе рекурсии.
- Критерии для определения задачи, подходящей для метода «разделяй и властвуй», включают: можно ли разбить задачу, независимы ли подзадачи, можно ли объединить решения подзадач.
- Сортировка слиянием является типичным применением стратегии «разделяй и властвуй», она рекурсивно разбивает массив на два подмассива равной длины, пока не останется один элемент, а затем начинает объединять их уровень за уровнем для завершения сортировки.
- Применение стратегии «разделяй и властвуй» часто может повысить эффективность алгоритма. С одной стороны, эта стратегия уменьшает количество операций; с другой стороны, разделение способствует параллельной оптимизации системы.
- «Разделяй и властвуй» может не только решать многие алгоритмические задачи, но и широко применяется в проектировании структур данных и алгоритмов, встречаясь повсеместно.
- По сравнению с полным перебором адаптивный поиск более эффективен. Алгоритмы поиска с временной сложностью $O(\log n)$ обычно реализуются на основе стратегии «разделяй и властвуй».
- Двоичный поиск является еще одним типичным применением стратегии «разделяй и властвуй», он не включает этап объединения решений подзадач. Мы можем реализовать двоичный поиск с помощью рекурсивного разделения.
- В задаче построения двоичного дерева построение дерева (исходная задача) можно разделить на построение левого и правого поддеревьев (подзадачи), что достигается путем разделения индексных интервалов прямого и центрального обходов.
- В задаче о Ханойских башнях задачу размера $n$ можно разделить на две подзадачи размера $n-1$ и одну подзадачу размера $1$. После последовательного решения этих трех подзадач исходная задача также решается.