--- 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$ в массиве. ![Процесс двоичного поиска в стиле разделяй и властвуй](binary_search_recur.assets/binary_search_recur.png){ 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 "Визуализация кода"
Во весь экран >