This commit is contained in:
krahets
2026-07-19 03:42:10 +08:00
parent cb5235bc3f
commit 2b8612bc41
238 changed files with 7477 additions and 3 deletions
@@ -0,0 +1,102 @@
---
comments: true
---
<!-- Автоматически создано utils/exercises/publish_exercises.py; не редактируйте напрямую. -->
# 12.6 &nbsp; Упражнения
## 12.6.1 &nbsp; Вопросы для самопроверки
### 1. &nbsp; Какие задачи подходят для метода «разделяй и властвуй»
Ученик хочет решить следующие задачи способом «разделить на две половины, решить каждую отдельно, затем объединить результаты».
Для каждой задачи выберите одно из утверждений: «подходит для метода “разделяй и властвуй”», «разделение возможно, но не уменьшает общий объем работы» или «половины нельзя решить независимо». Объясните свой выбор.
<!-- numbered-subquestions -->
1. Отсортировать неупорядоченный массив;
2. найти максимальный элемент массива;
3. последовательно выполнить набор операций стека `push(x)` и `pop()`, выводя элемент, полученный при каждой операции `pop()`.
??? success "Ответ"
1. Подходит: массив делится пополам, обе половины независимо сортируются, а затем сливаются за $O(n)$ — именно так работает сортировка слиянием.
2. Разделение возможно, но оно не уменьшит общий объем работы: в обеих половинах в сумме все равно нужно проверить все $n$ элементов,
поэтому, как и при прямом обходе, потребуется $O(n)$ времени.
3. Половины нельзя решить независимо: содержимое стека в начале второй половины операций зависит от результата первой,
поэтому решить их независимо, не зная результатов друг друга, невозможно.
### 2. &nbsp; Как быстрое возведение в степень уменьшает число вычислений
Рекурсивная функция ниже вычисляет $x^n$ методом «разделяй и властвуй»:
```python
def fast_pow(x, n):
if n == 0:
return 1
half = fast_pow(x, n // 2)
if n % 2 == 0:
return half * half
return half * half * x
```
Функция вызывается как `fast_pow(3, 5)`.
<!-- numbered-subquestions -->
1. Какие значения последовательно принимает параметр `n` при рекурсивных вызовах?
2. Какие значения поочередно возвращаются на каждом уровне, начиная с самого глубокого?
3. Почему результат нужно сначала сохранить в `half`, а не дважды записывать `fast_pow(x, n // 2)`?
??? success "Ответ"
1. Параметр последовательно принимает значения `5 → 2 → 1 → 0`: на каждом шаге показатель степени уменьшается вдвое до достижения условия завершения.
2. При `n = 0` возвращается 1; при `n = 1` возвращается $1×1×3=3$;
при `n = 2` — $3×3=9$; при `n = 5` — $9×9×3=243$.
3. Если записать `fast_pow(x, n // 2)` по обе стороны умножения, два рекурсивных вызова будут решать одну и ту же подзадачу.
Когда результат сохраняется в `half`, на каждом уровне выполняется только один рекурсивный вызов, а глубина рекурсии составляет примерно $\log n$;
два вызова привели бы к множеству повторных вычислений.
### 3. &nbsp; Разделение левого и правого поддеревьев по последовательностям обхода
Двоичное дерево без повторяющихся узлов имеет следующие последовательности прямого и симметричного обходов:
- прямой обход: `[A, B, D, E, C]`
- симметричный обход: `[D, B, E, A, C]`
Выполните только разделение на уровне корневого узла: продолжать рекурсию и рисовать все дерево не требуется.
<!-- numbered-subquestions -->
1. Какой узел является корневым?
2. Какие участки симметричного обхода соответствуют левому и правому поддеревьям?
3. Какие участки прямого обхода соответствуют левому и правому поддеревьям? Какие непосредственные дочерние узлы имеет корень?
??? success "Ответ"
1. Первый узел прямого обхода является корневым, поэтому корень — `A`.
2. Узел `A` делит симметричный обход на две части: левому поддереву соответствует `[D, B, E]`, правому — `[C]`.
3. Левое поддерево содержит 3 узла, поэтому следующие после корня `A` 3 элемента прямого обхода относятся к левому поддереву:
`[B, D, E]`. Оставшийся участок `[C]` относится к правому поддереву.
Следовательно, левый дочерний узел корня — `B`, а правый — `C`.
## 12.6.2 &nbsp; Задачи по программированию
### 1. &nbsp; Быстрое возведение в степень
Даны вещественное число `x` и целое число `n`. Вычислите $x^n$, не используя встроенную функцию возведения в степень.
Примените рекурсивный метод «разделяй и властвуй»: каждый раз уменьшайте показатель степени вдвое и повторно используйте уже вычисленный результат подзадачи.
В этой задаче $x^0=1$, в том числе при `x = 0`; если `n < 0`, гарантируется, что `x != 0`, а ответ можно преобразовать к $(1/x)^{-n}$.
??? tip "Подсказки"
1. При n, равном 0, ответ равен 1
2. Вычислив x в степени n // 2, сохраните результат в half и не выполняйте рекурсивный вызов второй раз
3. Если n < 0, сначала замените x на 1 / x, а n — на -n; в C++ или Java предварительно преобразуйте n в 64-битное целое число, чтобы избежать переполнения при смене знака у минимального 32-битного целого
[LeetCode](https://leetcode.com/problems/powx-n/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
@@ -20,3 +20,4 @@ icon: material/set-split
- [12.3 &nbsp; Задача построения двоичного дерева](build_binary_tree_problem.md)
- [12.4 &nbsp; Задача о Ханойской башне](hanota_problem.md)
- [12.5 &nbsp; Резюме](summary.md)
- [12.6 &nbsp; Упражнения](exercises.md)