mirror of
https://github.com/krahets/hello-algo.git
synced 2026-10-21 13:35:21 +08:00
* placeholder * 0413更新 * 0413删除comments * 第四章第一节进行一对一修改,发现几个核心问题,后续不再重复叙述。具体问题参见具体说明 因为该版本不会被用在最后的校对输出中,后续不再复制纸质版原文,只标注出现不一致的部分 1.非程序部分大量出现文本和纸质版不一致,已全部修改,AI翻译版本没有纸质版自然 2.程序部分python部分就和纸质版不一致,但是其他程序和AI翻译的python一致,建议让AI先搬运纸质版python的翻译,再加工其他的部分,翻译水平尚可。 3.程序部分AI翻译版本不稳定,大部分使用动宾结构表达,少部分使用动名词结构,出现少量语法错误。纸质版大多使用动名词结构 4.相对中文在线版格式未出现问题,该部分表现较好 * 第四章后续章节未精确修改和俄语版不一致的部分,因为从第一节可以看出,AI基本上都是自己翻的。 在这里标出了所有应该修改的部分 除了第一节以外新增的问题: 1.程序部分出现语法错误 2.单复数问题在程序和标题中出现多次,正文中更是屡见不鲜 3.程序部分,python错了之后,后续的程序全部参照python一起错,建议统一修改,还是参照python但是一起修改掉 * 第十章第一节整体检查 1.正文部分和纸质版的重叠仍然基本没有,都是AI翻译 2.程序部分除了python和纸质版翻译不一致以外,出现较为严重的术语错误 3.接下来的四节内容我将不再展示正文部分的不一致,以上已经可以充分说明了 * 第十章后续章节梳理(该部分仍然绝大部分的内容都和纸质版完全不一致,从措辞到表达方法都不一致,包括正文也包括程序) 考虑到还需要进行AI设计,在此没有重复标注 其他问题: 1.正文中有一些细节,比如说引号的删除、大小写的确认、 2.程序上也出现了术语错误和语法错误 * docs(ru): refine wording after sentence-level Chinese comparison --------- Co-authored-by: Veronica <[email protected]> Co-authored-by: krahets <[email protected]>
84 lines
8.9 KiB
Markdown
84 lines
8.9 KiB
Markdown
# Двоичный поиск
|
|
|
|
<u>Двоичный поиск (binary search)</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)$ времени и тоже обходится дорого.
|
|
- Двоичный поиск применим только к массивам. Для него нужен скачкообразный доступ к элементам, а в связном списке такой доступ малоэффективен, поэтому двоичный поиск не подходит для списков и структур данных, построенных на их основе.
|
|
- При малом объеме данных линейный поиск работает лучше. В линейном поиске на каждом шаге нужна всего одна операция сравнения. В двоичном поиске требуется 1 сложение, 1 деление, от 1 до 3 сравнений и еще 1 сложение или вычитание, то есть всего от 4 до 6 элементарных операций. Поэтому при небольшом $n$ линейный поиск может оказаться быстрее двоичного.
|