---
comments: true
---
# 12.2 Поисковая стратегия разделяй и властвуй
Мы уже знаем, что алгоритмы поиска делятся на две большие категории.
- **Полный перебор**: реализуется через обход структуры данных, временная сложность равна $O(n)$ .
- **Адаптивный поиск**: использует особую организацию данных или априорную информацию, временная сложность может достигать $O(\log n)$ и даже $O(1)$ .
На практике **алгоритмы поиска с временной сложностью $O(\log n)$ обычно реализуются на основе стратегии «разделяй и властвуй»**, например двоичный поиск и деревья.
- На каждом шаге двоичный поиск раскладывает задачу (поиск целевого элемента в массиве) на более мелкую задачу (поиск целевого элемента в одной половине массива), и этот процесс продолжается, пока массив не станет пустым или пока не будет найден целевой элемент.
- Деревья являются типичными представителями идей «разделяй и властвуй». В таких структурах данных, как двоичное дерево поиска, AVL-дерево и куча, временная сложность различных операций равна $O(\log n)$ .
Стратегия «разделяй и властвуй» для двоичного поиска выглядит следующим образом.
- **Задача раскладывается на части**: двоичный поиск рекурсивно разбивает исходную задачу (поиск в массиве) на подзадачу (поиск в одной половине массива), и это достигается сравнением среднего элемента с целевым значением.
- **Подзадачи независимы**: в двоичном поиске на каждом шаге обрабатывается только одна подзадача, и она не зависит от других подзадач.
- **Решения подзадач не нужно объединять**: двоичный поиск нацелен на поиск конкретного элемента, поэтому объединять решения подзадач не требуется. Как только подзадача решена, одновременно считается решенной и исходная задача.
Иными словами, стратегия «разделяй и властвуй» повышает эффективность поиска потому, что при полном переборе за один шаг удается исключить только один вариант, **тогда как при поиске на основе «разделяй и властвуй» за один шаг можно исключить половину вариантов**.
### 1. Реализация двоичного поиска на основе «разделяй и властвуй»
В предыдущих главах двоичный поиск реализовывался через итерацию. Теперь реализуем его с помощью стратегии «разделяй и властвуй», то есть через рекурсию.
!!! 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$ в массиве.
{ class="animation-figure" }
Рисунок 12-4 Процесс двоичного поиска в стиле разделяй и властвуй
В реализации кода мы объявляем рекурсивную функцию `dfs()` для решения задачи $f(i, j)$ :
=== "Python"
```python title="binary_search_recur.py"
def dfs(nums: list[int], target: int, i: int, j: int) -> int:
"""Бинарный поиск: задача f(i, j)"""
# Если интервал пуст, целевой элемент отсутствует, вернуть -1
if i > j:
return -1
# Вычислить индекс середины m
m = (i + j) // 2
if nums[m] < target:
# Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j)
elif nums[m] > target:
# Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1)
else:
# Целевой элемент найден, вернуть его индекс
return m
def binary_search(nums: list[int], target: int) -> int:
"""Бинарный поиск"""
n = len(nums)
# Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1)
```
=== "C++"
```cpp title="binary_search_recur.cpp"
/* Бинарный поиск: задача f(i, j) */
int dfs(vector &nums, int target, int i, int j) {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1;
}
// Вычислить индекс середины m
int m = (i + j) / 2;
if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
int binarySearch(vector &nums, int target) {
int n = nums.size();
// Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1);
}
```
=== "Java"
```java title="binary_search_recur.java"
/* Бинарный поиск: задача f(i, j) */
int dfs(int[] nums, int target, int i, int j) {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1;
}
// Вычислить индекс середины m
int m = (i + j) / 2;
if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
int binarySearch(int[] nums, int target) {
int n = nums.length;
// Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1);
}
```
=== "C#"
```csharp title="binary_search_recur.cs"
/* Бинарный поиск: задача f(i, j) */
int DFS(int[] nums, int target, int i, int j) {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1;
}
// Вычислить индекс середины m
int m = (i + j) / 2;
if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
return DFS(nums, target, m + 1, j);
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
return DFS(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
int BinarySearch(int[] nums, int target) {
int n = nums.Length;
// Решить задачу f(0, n-1)
return DFS(nums, target, 0, n - 1);
}
```
=== "Go"
```go title="binary_search_recur.go"
/* Бинарный поиск: задача f(i, j) */
func dfs(nums []int, target, i, j int) int {
// Если интервал пуст, это означает отсутствие целевого элемента, вернуть -1
if i > j {
return -1
}
// Вычислить средний индекс
m := i + ((j - i) >> 1)
// Сравнить середину и целевой элемент
if nums[m] < target {
// Если меньше, рекурсивно обрабатывать правую половину массива
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m+1, j)
} else if nums[m] > target {
// Если больше, рекурсивно обработать левую половину массива
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m-1)
} else {
// Целевой элемент найден, вернуть его индекс
return m
}
}
/* Бинарный поиск */
func binarySearch(nums []int, target int) int {
n := len(nums)
return dfs(nums, target, 0, n-1)
}
```
=== "Swift"
```swift title="binary_search_recur.swift"
/* Бинарный поиск: задача f(i, j) */
func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if i > j {
return -1
}
// Вычислить индекс середины m
let m = (i + j) / 2
if nums[m] < target {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums: nums, target: target, i: m + 1, j: j)
} else if nums[m] > target {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums: nums, target: target, i: i, j: m - 1)
} else {
// Целевой элемент найден, вернуть его индекс
return m
}
}
/* Бинарный поиск */
func binarySearch(nums: [Int], target: Int) -> Int {
// Решить задачу f(0, n-1)
dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1)
}
```
=== "JS"
```javascript title="binary_search_recur.js"
/* Бинарный поиск: задача f(i, j) */
function dfs(nums, target, i, j) {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1;
}
// Вычислить индекс середины m
const m = i + ((j - i) >> 1);
if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
function binarySearch(nums, target) {
const n = nums.length;
// Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1);
}
```
=== "TS"
```typescript title="binary_search_recur.ts"
/* Бинарный поиск: задача f(i, j) */
function dfs(nums: number[], target: number, i: number, j: number): number {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1;
}
// Вычислить индекс середины m
const m = i + ((j - i) >> 1);
if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
function binarySearch(nums: number[], target: number): number {
const n = nums.length;
// Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1);
}
```
=== "Dart"
```dart title="binary_search_recur.dart"
/* Бинарный поиск: задача f(i, j) */
int dfs(List nums, int target, int i, int j) {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1;
}
// Вычислить индекс середины m
int m = (i + j) ~/ 2;
if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
int binarySearch(List nums, int target) {
int n = nums.length;
// Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1);
}
```
=== "Rust"
```rust title="binary_search_recur.rs"
/* Бинарный поиск: задача f(i, j) */
fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if i > j {
return -1;
}
let m: i32 = i + (j - i) / 2;
if nums[m as usize] < target {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if nums[m as usize] > target {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
fn binary_search(nums: &[i32], target: i32) -> i32 {
let n = nums.len() as i32;
// Решить задачу f(0, n-1)
dfs(nums, target, 0, n - 1)
}
```
=== "C"
```c title="binary_search_recur.c"
/* Бинарный поиск: задача f(i, j) */
int dfs(int nums[], int target, int i, int j) {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1;
}
// Вычислить индекс середины m
int m = (i + j) / 2;
if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// Целевой элемент найден, вернуть его индекс
return m;
}
}
/* Бинарный поиск */
int binarySearch(int nums[], int target, int numsSize) {
int n = numsSize;
// Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1);
}
```
=== "Kotlin"
```kotlin title="binary_search_recur.kt"
/* Бинарный поиск: задача f(i, j) */
fun dfs(
nums: IntArray,
target: Int,
i: Int,
j: Int
): Int {
// Если интервал пуст, целевой элемент отсутствует, вернуть -1
if (i > j) {
return -1
}
// Вычислить индекс середины m
val m = (i + j) / 2
return if (nums[m] < target) {
// Рекурсивная подзадача f(m+1, j)
dfs(nums, target, m + 1, j)
} else if (nums[m] > target) {
// Рекурсивная подзадача f(i, m-1)
dfs(nums, target, i, m - 1)
} else {
// Целевой элемент найден, вернуть его индекс
m
}
}
/* Бинарный поиск */
fun binarySearch(nums: IntArray, target: Int): Int {
val n = nums.size
// Решить задачу f(0, n-1)
return dfs(nums, target, 0, n - 1)
}
```
=== "Ruby"
```ruby title="binary_search_recur.rb"
### Бинарный поиск: задача f(i, j) ###
def dfs(nums, target, i, j)
# Если интервал пуст, целевой элемент отсутствует, вернуть -1
return -1 if i > j
# Вычислить индекс середины m
m = (i + j) / 2
if nums[m] < target
# Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j)
elsif nums[m] > target
# Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1)
else
# Целевой элемент найден, вернуть его индекс
return m
end
end
### Бинарный поиск ###
def binary_search(nums, target)
n = nums.length
# Решить задачу f(0, n-1)
dfs(nums, target, 0, n - 1)
end
```
??? pythontutor "Визуализация кода"