mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-17 22:10:59 +00:00
First version.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# Двоичный поиск
|
||||
|
||||
<u>Двоичный (бинарный) поиск</u> -- это эффективный алгоритм поиска, основанный на стратегии «разделяй и властвуй». Он использует упорядоченность данных, сокращая на каждом шаге область поиска вдвое, пока не будет найден целевой элемент или область поиска не станет пустой.
|
||||
|
||||
!!! question
|
||||
|
||||
Дан массив `nums` длиной $n$, элементы которого расположены в порядке возрастания и не повторяются. Необходимо найти и вернуть индекс элемента `target` в этом массиве. Если массив не содержит этот элемент, вернуть $-1$. Пример показан на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
Как показано на рисунке ниже, сначала инициализируются указатели $i = 0$ и $j = n - 1$, которые указывают на первый и последний элементы массива и представляют область поиска $[0, n - 1]$. Обратите внимание, что квадратные скобки обозначают замкнутый интервал, включающий граничные значения.
|
||||
|
||||
Затем в цикле выполняются следующие два шага:
|
||||
|
||||
1. Вычисляется индекс средней точки $m = \lfloor (i + j)/2 \rfloor$, где $\lfloor \: \rfloor$ обозначает операцию округления вниз.
|
||||
2. Определяется соотношение между `nums[m]` и `target`, выделяются три случая:
|
||||
1. Если `nums[m] < target`, то `target` находится в интервале $[m + 1, j]$, поэтому выполняется $i = m + 1$.
|
||||
2. Если `nums[m] > target`, то `target` находится в интервале $[i, m - 1]$, поэтому выполняется $j = m - 1$.
|
||||
3. Если `nums[m] = target`, то `target` найден, и возвращается индекс $m$.
|
||||
|
||||
Если массив не содержит целевой элемент, область поиска в конечном итоге сократится до пустой. В этом случае возвращается $-1$.
|
||||
|
||||
=== "<1>"
|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
|
||||
=== "<3>"
|
||||

|
||||
|
||||
=== "<4>"
|
||||

|
||||
|
||||
=== "<5>"
|
||||

|
||||
|
||||
=== "<6>"
|
||||

|
||||
|
||||
=== "<7>"
|
||||

|
||||
|
||||
Следует отметить, что, поскольку $i$ и $j$ имеют тип `int`, **сумма $i + j$ может превысить допустимый диапазон значений типа `int`**. Чтобы избежать переполнения, обычно для вычисления средней точки используется формула $m = \lfloor i + (j - i)/2 \rfloor$.
|
||||
|
||||
Ниже приведен пример кода.
|
||||
|
||||
```src
|
||||
[file]{binary_search}-[class]{}-[func]{binary_search}
|
||||
```
|
||||
|
||||
**Временная сложность составляет $O(\log n)$**: в цикле двоичного поиска область поиска сокращается вдвое на каждом шаге, поэтому количество итераций равно $\log_2 n$.
|
||||
|
||||
**Пространственная сложность составляет $O(1)$**: указатели $i$ и $j$ занимают постоянное количество памяти.
|
||||
|
||||
## Методы представления интервалов
|
||||
|
||||
Кроме указанного выше двойного замкнутого интервала, существует также левозамкнутый правооткрытый интервал $[0, n)$, т. е. левая граница включается, а правая -- нет. В этом представлении интервал $[i, j)$ пуст, когда $i = j$.
|
||||
|
||||
На основе этого представления можно реализовать двоичный поиск с аналогичной функциональностью.
|
||||
|
||||
```src
|
||||
[file]{binary_search}-[class]{}-[func]{binary_search_lcro}
|
||||
```
|
||||
|
||||
В двух представлениях интервалов инициализация, условия цикла и операции сокращения интервала в алгоритме двоичного поиска различаются, как показано на рисунке ниже.
|
||||
|
||||
Поскольку в представлении «двойной замкнутый интервал» обе границы определены как замкнутые, операции сокращения интервала с помощью указателей $i$ и $j$ также симметричны. Это снижает вероятность ошибок, поэтому обычно **рекомендуется использовать запись «двойной замкнутый интервал»**.
|
||||
|
||||

|
||||
|
||||
## Преимущества и ограничения
|
||||
|
||||
Двоичный поиск обладает хорошей производительностью как по времени, так и по пространству.
|
||||
|
||||
- Двоичный поиск отличается высокой эффективностью по времени. При большом объеме данных логарифмическая временная сложность имеет значительное преимущество. Например, при размере данных $n = 2^{20}$ линейный поиск требует $2^{20} = 1048576$ итераций, тогда как двоичный поиск -- всего $\log_2 2^{20} = 20$ итераций.
|
||||
- Двоичный поиск, в отличие от некоторых других алгоритмов поиска (например, хеш-поиска), не требует дополнительного пространства и поэтому более экономичен в плане использования памяти.
|
||||
|
||||
Тем не менее двоичный поиск не подходит для всех случаев по следующим основным причинам.
|
||||
|
||||
- Двоичный поиск применим только к упорядоченным данным. Если входные данные неупорядоченные, то их сортировка специально для использования двоичного поиска не оправдана. Это связано с тем, что временная сложность алгоритмов сортировки обычно составляет $O(n \log n)$, что выше, чем у линейного и двоичного поиска. В сценариях с частыми добавлениями элементов для поддержания упорядоченности массива необходимо вставлять элементы в определенные позиции, что имеет временную сложность $O(n)$ и также является весьма затратным.
|
||||
- Двоичный поиск применим только к массивам, поскольку требует скачкообразного (непрерывного) доступа к элементам. В связных списках выполнение скачкообразного доступа менее эффективно, поэтому такой поиск не подходит для применения в связных списках и структурах данных, основанных на них.
|
||||
- При небольших объемах данных линейный поиск более эффективен. В линейном поиске на каждом этапе требуется только одна операция сравнения; в двоичном поиске требуется одна операция сложения, одна операция деления, от одной до трех операций сравнения и одна операция сложения (вычитания), всего от четырех до шести элементарных операций. Поэтому, когда объем данных $n$ невелик, линейный поиск оказывается быстрее двоичного.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Поиск границ с использованием двоичного поиска
|
||||
|
||||
## Поиск левой границы
|
||||
|
||||
!!! question
|
||||
|
||||
Дан упорядоченный массив `nums` длиной $n$, который может содержать повторяющиеся элементы. Необходимо вернуть индекс самого левого элемента `target` в массиве. Если массив не содержит этот элемент, вернуть $-1$.
|
||||
|
||||
Вспомним метод поиска точки вставки с использованием двоичного поиска: после завершения поиска $i$ указывает на самый левый `target`, **поэтому поиск точки вставки по сути является поиском индекса самого левого** `target`.
|
||||
|
||||
Рассмотрим реализацию поиска левой границы через функцию поиска точки вставки. Обратите внимание, что массив может не содержать `target`, что может привести к следующим двум результатам:
|
||||
|
||||
- Индекс точки вставки $i$ выходит за границы массива.
|
||||
- Элемент `nums[i]` не равен `target`.
|
||||
|
||||
При возникновении любой из этих двух ситуаций можно просто вернуть $-1$. Код приведен ниже:
|
||||
|
||||
```src
|
||||
[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge}
|
||||
```
|
||||
|
||||
## Поиск правой границы
|
||||
|
||||
Как же найти самый правый `target`? Самый прямой способ -- изменить код, заменив операцию сужения указателей в случае `nums[m] == target`. Код здесь опущен, заинтересованные читатели могут реализовать его самостоятельно.
|
||||
|
||||
Ниже мы рассмотрим два более изящных метода.
|
||||
|
||||
### Повторное использование поиска левой границы
|
||||
|
||||
На самом деле мы можем использовать функцию поиска самого левого элемента для поиска самого правого элемента, конкретный метод: **преобразовать поиск самого правого** `target` **в поиск самого левого** `target + 1`.
|
||||
|
||||
Как показано на рисунке ниже, после завершения поиска указатель $i$ указывает на самый левый `target + 1` (если он существует), а $j$ указывает на самый правый `target`, **поэтому достаточно вернуть** $j$.
|
||||
|
||||

|
||||
|
||||
Обратите внимание, что возвращаемая точка вставки -- это $i$, поэтому необходимо вычесть из нее $1$, чтобы получить $j$:
|
||||
|
||||
```src
|
||||
[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge}
|
||||
```
|
||||
|
||||
### Преобразование в поиск элемента
|
||||
|
||||
Мы знаем, что когда массив не содержит `target`, в конечном итоге $i$ и $j$ будут указывать соответственно на первый элемент, больший и меньший `target`.
|
||||
|
||||
Поэтому, как показано на рисунке ниже, мы можем создать элемент, которого не существует в массиве, для поиска левой и правой границ.
|
||||
|
||||
- Поиск самого левого `target`: можно преобразовать в поиск `target - 0.5` и вернуть указатель $i$.
|
||||
- Поиск самого правого `target`: можно преобразовать в поиск `target + 0.5` и вернуть указатель $j$.
|
||||
|
||||

|
||||
|
||||
Код здесь опущен, следует отметить следующие два момента:
|
||||
|
||||
- Данный массив не содержит дробных чисел, что означает, что нам не нужно беспокоиться о том, как обрабатывать случай равенства.
|
||||
- Поскольку этот метод вводит дробные числа, необходимо изменить тип переменной `target` в функции на тип с плавающей точкой (в Python изменения не требуются).
|
||||
@@ -0,0 +1,91 @@
|
||||
# Вставка с использованием двоичного поиска
|
||||
|
||||
Двоичный поиск можно использовать не только для поиска целевого элемента, но и для решения множества других задач, таких как поиск позиции для вставки целевого элемента.
|
||||
|
||||
## Случай без повторяющихся элементов
|
||||
|
||||
!!! question
|
||||
|
||||
Дан упорядоченный массив `nums` длиной $n$ и элемент `target`, массив не содержит повторяющихся элементов. Необходимо вставить `target` в массив `nums`, сохранив его упорядоченность. Если массив уже содержит элемент `target`, вставить его слева от существующего. Вернуть индекс `target` в массиве после вставки. Пример показан на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
Если требуется повторно использовать код двоичного поиска из предыдущего раздела, необходимо ответить на следующие два вопроса:
|
||||
|
||||
**Вопрос первый**: если массив содержит `target`, является ли индекс вставки индексом этого элемента?
|
||||
|
||||
Условие задачи требует вставить `target` слева от равного элемента, т. е. новый `target` заменяет старое положение `target`. То есть **если массив уже содержит `target`, индекс вставки совпадает с индексом этого `target`**.
|
||||
|
||||
**Вопрос второй**: если массив не содержит `target`, какой элемент будет иметь индекс вставки?
|
||||
|
||||
Дальнейший процесс двоичного поиска: когда `nums[m] < target`, указатель $i$ перемещается, т. е. приближается к элементу, большему или равному `target`. Аналогично указатель $j$ всегда приближается к элементу, меньшему или равному `target`.
|
||||
|
||||
Таким образом, по окончании двоичного поиска указатель $i$ указывает на первый элемент, больший `target`, а указатель $j$ -- на первый элемент, меньший `target`. **Легко понять, что, если массив не содержит `target`, индекс вставки будет равен $i$**. Ниже приведен пример кода.
|
||||
|
||||
```src
|
||||
[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple}
|
||||
```
|
||||
|
||||
## Случай с повторяющимися элементами
|
||||
|
||||
!!! question
|
||||
|
||||
На основе предыдущей задачи, предположим, что массив может содержать повторяющиеся элементы, остальные условия остаются неизменными.
|
||||
|
||||
Если в массиве существует несколько одинаковых `target`, то обычный двоичный поиск может вернуть индекс только одного из них, **не определяя, сколько `target` находится слева и справа от этого элемента**.
|
||||
|
||||
Задача требует вставить целевой элемент в самое левое положение, **поэтому необходимо найти индекс самого левого `target` в массиве**. Первоначально предполагается реализовать решение следующим образом (см. рисунок ниже):
|
||||
|
||||
1. Выполнить двоичный поиск и получить индекс любого `target`, обозначить его как $k$.
|
||||
2. Начиная с индекса $k$, выполнить линейный обход влево и вернуть индекс, когда будет найден самый левый `target`.
|
||||
|
||||

|
||||
|
||||
Это рабочий метод, но он включает линейный поиск, поэтому его временная сложность составляет $O(n)$. Когда в массиве много повторяющихся `target`, эффективность этого метода низка.
|
||||
|
||||
Теперь рассмотрим расширение алгоритма двоичного поиска. Общий процесс остается неизменным: на каждом этапе сначала вычисляется средний индекс $m$, затем определяется отношение между `target` и `nums[m]`. Возможны следующие случаи:
|
||||
|
||||
- Когда `nums[m] < target` или `nums[m] > target`, тогда `target` еще не найден, поэтому используется операция сужения интервала обычного двоичного поиска, **чтобы указатели $i$ и $j$ приближались к `target`**.
|
||||
- Когда `nums[m] == target`, тогда элементы, меньшие `target`, находятся в интервале $[i, m - 1]$. Поэтому используется операция $j = m - 1$ для сужения интервала, **чтобы указатель $j$ приблизился к элементам, меньшим `target`**.
|
||||
|
||||
После завершения цикла $i$ будет указывать на самый левый `target`, а $j$ -- на первый элемент, меньший `target`. **Поэтому индекс $i$ является точкой вставки**.
|
||||
|
||||
=== "<1>"
|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
|
||||
=== "<3>"
|
||||

|
||||
|
||||
=== "<4>"
|
||||

|
||||
|
||||
=== "<5>"
|
||||

|
||||
|
||||
=== "<6>"
|
||||

|
||||
|
||||
=== "<7>"
|
||||

|
||||
|
||||
=== "<8>"
|
||||

|
||||
|
||||
Рассмотрим следующий код: ветви условий `nums[m] > target` и `nums[m] == target` выполняют одинаковые операции, поэтому их можно объединить.
|
||||
|
||||
Тем не менее мы можем оставить условия развернутыми, поскольку логика становится более ясной и читаемой.
|
||||
|
||||
```src
|
||||
[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
|
||||
Код в этом разделе использует запись «двойной замкнутый интервал». Заинтересованные читатели могут самостоятельно реализовать вариант «левозамкнутый правооткрытый интервал».
|
||||
|
||||
В целом двоичный поиск заключается в установке целей поиска для указателей $i$ и $j$. Целью может быть конкретный элемент (например, `target`) или диапазон элементов (например, элементы, меньшие `target`).
|
||||
|
||||
В процессе непрерывного циклического деления указатели $i$ и $j$ постепенно приближаются к заранее установленной цели. В конечном итоге они либо успешно находят ответ, либо останавливаются после пересечения границ.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Поиск
|
||||
|
||||

|
||||
|
||||
!!! abstract
|
||||
|
||||
Поиск — это путешествие в неизвестность, где нам может потребоваться обойти каждый уголок таинственного пространства, а может быть, мы сможем быстро найти цель.
|
||||
|
||||
В этом путешествии поиска каждое исследование может привести к неожиданному ответу.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Стратегия оптимизации с использованием хеширования
|
||||
|
||||
В задачах по алгоритмам **мы часто снижаем временную сложность алгоритма, заменяя линейный поиск на хеш-поиск**. Давайте углубим понимание на примере алгоритмической задачи.
|
||||
|
||||
!!! question
|
||||
|
||||
Дан массив целых чисел `nums` и целевой элемент `target`. Необходимо найти в массиве два элемента, "сумма" которых равна `target`, и вернуть их индексы в массиве. Можно вернуть любое решение.
|
||||
|
||||
## Линейный поиск: обмен времени на пространство
|
||||
|
||||
Рассмотрим прямой перебор всех возможных комбинаций. Как показано на рисунке ниже, мы запускаем двойной цикл, на каждой итерации проверяя, равна ли сумма двух целых чисел `target`, и если да, возвращаем их индексы.
|
||||
|
||||

|
||||
|
||||
Код выглядит следующим образом:
|
||||
|
||||
```src
|
||||
[file]{two_sum}-[class]{}-[func]{two_sum_brute_force}
|
||||
```
|
||||
|
||||
Временная сложность этого метода составляет $O(n^2)$, пространственная сложность -- $O(1)$. При больших объемах данных это очень затратно по времени.
|
||||
|
||||
## Хеш-поиск: обмен пространства на время
|
||||
|
||||
Рассмотрим использование хеш-таблицы, где ключами и значениями будут элементы массива и их индексы соответственно. Проходим по массиву в цикле, на каждой итерации выполняя шаги, показанные на рисунке ниже.
|
||||
|
||||
1. Проверяем, находится ли число `target - nums[i]` в хеш-таблице. Если да, сразу возвращаем индексы этих двух элементов.
|
||||
2. Добавляем в хеш-таблицу пару ключ-значение: `nums[i]` и индекс `i`.
|
||||
|
||||
=== "<1>"
|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
|
||||
=== "<3>"
|
||||

|
||||
|
||||
Реализация кода приведена ниже, требуется только один цикл:
|
||||
|
||||
```src
|
||||
[file]{two_sum}-[class]{}-[func]{two_sum_hash_table}
|
||||
```
|
||||
|
||||
Этот метод с помощью хеш-поиска снижает временную сложность с $O(n^2)$ до $O(n)$, значительно повышая эффективность выполнения.
|
||||
|
||||
Поскольку необходимо поддерживать дополнительную хеш-таблицу, пространственная сложность составляет $O(n)$. **Несмотря на это, данный метод обеспечивает более сбалансированную общую эффективность по времени и пространству, поэтому он является оптимальным решением для этой задачи**.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Переосмысление алгоритмов поиска
|
||||
|
||||
<u>Алгоритм поиска (searching algorithm)</u> используется для поиска одного или группы элементов, удовлетворяющих определенным условиям, в структуре данных (например, массиве, связном списке, дереве или графе).
|
||||
|
||||
Алгоритмы поиска можно разделить на следующие две категории в зависимости от подхода к реализации:
|
||||
|
||||
- **Определение целевых элементов путем обхода структуры данных**, например, обход массивов, связных списков, деревьев и графов.
|
||||
- **Использование организационной структуры данных или априорной информации, содержащейся в данных, для эффективного поиска элементов**, например, двоичный поиск, хеш-поиск и поиск в двоичном дереве поиска.
|
||||
|
||||
Нетрудно заметить, что все эти темы уже были рассмотрены в предыдущих главах, поэтому алгоритмы поиска для нас не новы. В этом разделе мы рассмотрим алгоритмы поиска с более систематической точки зрения.
|
||||
|
||||
## Поиск методом перебора
|
||||
|
||||
Поиск методом перебора определяет целевые элементы путем обхода каждого элемента структуры данных.
|
||||
|
||||
- "Линейный поиск" применим к линейным структурам данных, таким как массивы и связные списки. Он начинается с одного конца структуры данных, последовательно обращается к элементам, пока не будет найден целевой элемент или не будет достигнут другой конец без нахождения целевого элемента.
|
||||
- "Поиск в ширину" и "поиск в глубину" -- это две стратегии обхода графов и деревьев. Поиск в ширину начинается с начального узла и выполняет послойный поиск, обращаясь к узлам от ближних к дальним. Поиск в глубину начинается с начального узла, идет по пути до конца, затем возвращается назад и пробует другие пути, пока не будет пройдена вся структура данных.
|
||||
|
||||
Преимущество поиска методом перебора заключается в простоте и хорошей универсальности, **не требуется предварительная обработка данных и использование дополнительных структур данных**.
|
||||
|
||||
Однако **временная сложность таких алгоритмов составляет $O(n)$**, где $n$ -- количество элементов, поэтому производительность низкая при больших объемах данных.
|
||||
|
||||
## Адаптивный поиск
|
||||
|
||||
Адаптивный поиск использует специфические свойства данных (например, упорядоченность) для оптимизации процесса поиска, что позволяет более эффективно определять целевые элементы.
|
||||
|
||||
- "Двоичный поиск" использует упорядоченность данных для эффективного поиска и применим только к массивам.
|
||||
- "Хеш-поиск" использует хеш-таблицу для установления соответствия между поисковыми данными и целевыми данными в виде пар ключ-значение, что позволяет реализовать операции запроса.
|
||||
- "Поиск в дереве" в специфических древовидных структурах (например, двоичном дереве поиска) основан на сравнении значений узлов для быстрого исключения узлов и определения целевого элемента.
|
||||
|
||||
Преимущество таких алгоритмов заключается в высокой эффективности, **временная сложность может достигать $O(\log n)$ или даже $O(1)$**.
|
||||
|
||||
Однако **использование этих алгоритмов часто требует предварительной обработки данных**. Например, двоичный поиск требует предварительной сортировки массива, хеш-поиск и поиск в дереве требуют дополнительных структур данных, а поддержание этих структур данных также требует дополнительных временных и пространственных затрат.
|
||||
|
||||
!!! tip
|
||||
|
||||
Адаптивные алгоритмы поиска часто называют алгоритмами поиска, **они в основном используются для быстрого извлечения целевых элементов в определенных структурах данных**.
|
||||
|
||||
## Выбор метода поиска
|
||||
|
||||
Для заданного набора данных размером $n$ мы можем использовать линейный поиск, двоичный поиск, поиск в дереве, хеш-поиск и другие методы для поиска целевого элемента. Принцип работы каждого метода показан на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
Эффективность операций и характеристики вышеуказанных методов приведены в следующей таблице.
|
||||
|
||||
<p align="center"> Таблица <id> Сравнение эффективности алгоритмов поиска </p>
|
||||
|
||||
| | Линейный поиск | Двоичный поиск | Поиск в дереве | Хеш-поиск |
|
||||
| ------------------------ | -------------- | ------------------ | ------------------ | --------------- |
|
||||
| Поиск элемента | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ |
|
||||
| Вставка элемента | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ |
|
||||
| Удаление элемента | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ |
|
||||
| Дополнительное пространство | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ |
|
||||
| Предварительная обработка данных | / | Сортировка $O(n \log n)$ | Построение дерева $O(n \log n)$ | Построение хеш-таблицы $O(n)$ |
|
||||
| Упорядоченность данных | Неупорядоченные | Упорядоченные | Упорядоченные | Неупорядоченные |
|
||||
|
||||
Выбор алгоритма поиска также зависит от масштаба, требований к производительности поиска, частоты запросов и обновлений данных.
|
||||
|
||||
**Линейный поиск**
|
||||
|
||||
- Хорошая универсальность, не требуется никаких операций предварительной обработки данных. Если нам нужно выполнить запрос данных только один раз, то время предварительной обработки данных для других трех методов будет больше, чем время линейного поиска.
|
||||
- Подходит для небольших объемов данных, в этом случае временная сложность оказывает меньшее влияние на эффективность.
|
||||
- Подходит для сценариев с высокой частотой обновления данных, поскольку этот метод не требует дополнительного обслуживания данных.
|
||||
|
||||
**Двоичный поиск**
|
||||
|
||||
- Подходит для больших объемов данных, демонстрирует стабильную эффективность, наихудшая временная сложность составляет $O(\log n)$.
|
||||
- Объем данных не может быть слишком большим, поскольку для хранения массива требуется непрерывное пространство памяти.
|
||||
- Не подходит для сценариев с частым добавлением и удалением данных, поскольку затраты на поддержание упорядоченного массива довольно высоки.
|
||||
|
||||
**Хеш-поиск**
|
||||
|
||||
- Подходит для сценариев с очень высокими требованиями к производительности запросов, средняя временная сложность составляет $O(1)$.
|
||||
- Не подходит для сценариев, требующих упорядоченных данных или поиска по диапазону, поскольку хеш-таблица не может поддерживать упорядоченность данных.
|
||||
- Высокая зависимость от хеш-функции и стратегии обработки хеш-коллизий, существует значительный риск снижения производительности.
|
||||
- Не подходит для слишком больших объемов данных, поскольку хеш-таблица требует дополнительного пространства для максимального уменьшения коллизий и обеспечения хорошей производительности запросов.
|
||||
|
||||
**Поиск в дереве**
|
||||
|
||||
- Подходит для огромных объемов данных, поскольку узлы дерева хранятся в памяти разрозненно.
|
||||
- Подходит для сценариев, требующих поддержания упорядоченных данных или поиска по диапазону.
|
||||
- В процессе непрерывного добавления и удаления узлов двоичное дерево поиска может стать несбалансированным, временная сложность ухудшается до $O(n)$.
|
||||
- При использовании AVL-дерева или красно-черного дерева все операции могут стабильно выполняться с эффективностью $O(\log n)$, но операции по поддержанию баланса дерева добавляют дополнительные затраты.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Резюме
|
||||
|
||||
### Основные моменты
|
||||
|
||||
- Двоичный поиск основан на упорядоченности данных и постепенно сокращает область поиска вдвое с помощью цикла. Он требует упорядоченных входных данных и применим только к массивам или структурам данных, реализованным на основе массивов.
|
||||
- Поиск методом перебора осуществляется путем обхода структуры данных для определения местоположения данных. Линейный поиск применим к массивам и связным спискам, поиск в ширину и поиск в глубину применимы к графам и деревьям. Эти алгоритмы обладают хорошей универсальностью, не требуют предварительной обработки данных, но имеют высокую временную сложность $O(n)$.
|
||||
- Хеш-поиск, поиск по дереву и двоичный поиск относятся к эффективным методам поиска, которые могут быстро находить целевые элементы в определенных структурах данных. Эти алгоритмы высокоэффективны, временная сложность может достигать $O(\log n)$ и даже $O(1)$, но обычно требуют дополнительных структур данных.
|
||||
- На практике необходимо проводить конкретный анализ таких факторов, как объем данных, требования к производительности поиска, частота запросов и обновлений данных, чтобы выбрать подходящий метод поиска.
|
||||
- Линейный поиск подходит для небольших или часто обновляемых данных; двоичный поиск подходит для больших упорядоченных данных; хеш-поиск подходит для данных с высокими требованиями к эффективности запросов и без необходимости диапазонных запросов; поиск по дереву подходит для больших динамических данных, требующих поддержания порядка и диапазонных запросов.
|
||||
- Замена линейного поиска хеш-поиском является распространенной стратегией оптимизации времени выполнения, которая может снизить временную сложность с $O(n)$ до $O(1)$.
|
||||
Reference in New Issue
Block a user