Поиск: линейный и двоичный
Чтобы найти одно имя в отсортированном списке из миллиона, двоичному поиску никогда не нужно больше 20 проверок.
Ищем одно значение
Задача поиска состоит в том, чтобы найти, где значение лежит в наборе данных, или узнать, что его там нет. Телефон делает это, когда ты набираешь имя контакта, магазин делает это, когда проверяет, есть ли товар на складе, а проверка орфографии делает это для каждого набранного слова. На входе список и искомое значение. На выходе позиция искомого значения или знак вроде -1, который значит «не найдено».
Линейный поиск: по одному
Самый простой способ: начать слева и проверять значения по очереди, пока не найдёшь искомое или не кончится список. Это линейный поиск. Он работает на любом списке, отсортированном или нет. Его стоимость зависит от удачи: если искомое значение первое, хватит одной проверки; если оно последнее или его нет, проверишь каждое значение. Для списка из n значений в худшем случае нужно n проверок. Вдвое более длинному списку может понадобиться вдвое больше проверок, отсюда и название «линейный»: работа растёт по прямой вместе с размером.
function linearSearch(list, target) {
for (let i = 0; i < list.length; i++) {
if (list[i] === target) return i; // нашли
}
return -1; // проверили всё
}Двоичный поиск: дели остаток пополам
Если список отсортирован, можно действовать гораздо лучше. Вспомни игру «угадай число»: кто-то загадывает число от 1 до 100 и отвечает только «больше» или «меньше». Разумный игрок сначала называет 50. Каков бы ни был ответ, половина вариантов отпадает. Следующая попытка берёт середину того, что осталось, и так далее. Двоичный поиск делает ровно это с отсортированным списком. Он держит две границы, lo и hi, вокруг части, где искомое значение ещё может быть. Он проверяет среднее значение. Если это искомое, работа закончена. Если среднее значение меньше искомого, то и всё слева от него меньше, поэтому lo переходит за середину. Если больше, hi переходит перед серединой. Когда lo заходит за hi, ничего не осталось, и искомого значения в списке нет.
function binarySearch(list, target) {
let lo = 0, hi = list.length - 1;
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
if (list[mid] === target) return mid;
if (list[mid] < target) lo = mid + 1; // искомое справа
else hi = mid - 1; // искомое слева
}
return -1;
}Перетащи, чтобы повернуть. Колёсико или щипок для масштаба.
Сколько проверок?
Каждая проверка двоичного поиска как минимум вдвое уменьшает оставшуюся часть. Если начать с n значений, после одной проверки останется не больше примерно n/2, после двух примерно n/4, и так далее, пока не останется одно значение. Число делений пополам, нужных, чтобы дойти от n до 1, равно двоичному логарифму n, его записывают log₂ n. Если учесть и последнюю проверку, двоичному поиску нужно не больше ⌊log₂ n⌋ + 1 проверок, где ⌊ ⌋ означает округление вниз. Для 100 значений это 7 проверок, поэтому в «угадай число» всегда можно выиграть за 7 попыток. Для 1000 значений это 10, для миллиона 20, а для миллиарда 30.
| Размер списка n | Линейный поиск, худший случай | Двоичный поиск, худший случай |
|---|---|---|
| 7 | 7 | 3 |
| 100 | 100 | 7 |
| 1,000 | 1,000 | 10 |
| 1,000,000 | 1,000,000 | 20 |
| 1,000,000,000 | 1,000,000,000 | 30 |
Подвох: список должен быть отсортирован
Двоичный поиск работает только потому, что в отсортированном списке одно сравнение говорит, с какой стороны должно быть искомое значение. В неотсортированном списке среднее значение ничего не говорит о соседях, и ты возвращаешься к линейному поиску. Сортировка сама требует работы (о ней следующие два урока), поэтому выбор зависит от применения. Если ищешь в списке один раз, линейный поиск может оказаться самым дешёвым. Если ищешь в одном и том же списке тысячи раз, один раз отсортировать его, а потом пользоваться двоичным поиском быстро окупается. Этот обмен между подготовкой данных и ответами на вопросы о них проходит через весь предмет.
lo = mid вместо lo = mid + 1, цикл может работать вечно, когда осталось два значения, потому что mid не меняется. Среднее значение уже проверено, поэтому его можно исключить из следующего диапазона.| линейный поиск | Проверять значения по одному с начала |
| двоичный поиск | Проверять середину отсортированного диапазона и отбрасывать половину, где искомого быть не может |
| худший случай | Входные данные, на которых алгоритм делает больше всего работы |
| log₂ n | Сколько раз n можно поделить пополам, прежде чем получится 1 |
| lo и hi | Два конца диапазона, где искомое значение ещё может быть |
| -1 | Обычное возвращаемое значение, которое значит «не найдено» |
- Сколько проверок самое большее нужно двоичному поиску для отсортированного списка из 1000 значений?
- Сколько проверок самое большее нужно линейному поиску для списка из 1000 значений?
- Для двоичного поиска список должен быть
- отсортирован
- коротким
- из целых чисел
- без повторяющихся значений
- Сколько проверок сделает двоичный поиск, чтобы найти 23 в списке [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]?
- Сколько проверок сделает линейный поиск, чтобы найти 23 в том же списке?
- Двоичный поиск ищет 25 и проверяет среднее значение 40. Что дальше?
- hi переходит прямо перед серединой
- lo переходит прямо за середину
- поиск останавливается: 25 нет
- проверяется следующее значение справа
- Ты угадываешь число от 1 до 100, и тебе говорят «больше» или «меньше». Сколько попыток при лучшей стратегии всегда хватает?
- Список стал вдвое длиннее. Что происходит с числом проверок двоичного поиска в худшем случае?
- Оно удваивается
- Оно растёт примерно на одну
- Оно остаётся точно таким же
- Оно возводится в квадрат
- Тебе нужно один раз найти одно значение в неотсортированном списке. Что дешевле?
- Линейный поиск
- Отсортировать список, затем двоичный поиск
- Почему из-за
lo = mid(вместоmid + 1) двоичный поиск может зациклиться?- Когда осталось два значения, mid может каждый круг оставаться прежним
- mid становится отрицательным
- JavaScript этого не разрешает
- Список становится длиннее
- Сколько проверок самое большее нужно двоичному поиску для 1 000 000 значений?
- Линейный поиск проверяет значения по одному: до n проверок.
- Двоичный поиск делит оставшийся диапазон пополам: до ⌊log₂ n⌋ + 1 проверок.
- Двоичный поиск работает только на отсортированном списке.
- Миллион значений: самое большее 20 проверок.
- Обновляй через mid + 1 и mid − 1, чтобы диапазон всегда сужался.
Источники курса (11)
- MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
- lecture notes 1 Introduction, 3 Sorting, 9 Breadth-First Search, 10 Depth-First Search (ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes) and Fall 2011 lecture 13 BFS notes (ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011)
- Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (MIT Press)
- with the CLRS-based Khan Academy algorithms unit by Thomas Cormen and Devin Balkcom of Dartmouth (khanacademy.org/computing/computer-science/algorithms: binary search, selection sort analysis, merge sort, quicksort overview and analysis)
- MacTutor History of Mathematics, Al-Khwarizmi (mathshistory.st-andrews.ac.uk/Biographies/Al-Khwarizmi: word algorithm from the Latin title Algoritmi de numero Indorum)
- D. E. Joyce, Euclid's Elements Book VII Propositions 1 and 2 (mathcs.clarku.edu/~djoyce/elements/bookVII)
- Wolfram MathWorld, Merge Sort (mathworld.wolfram.com/MergeSort.html: proposed by John von Neumann in 1945, after Knuth 1998 p. 159)
- Stanford CS "Tony Hoare: Quicksort" (cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/quicksort.html: devised while a student at Moscow State University for Russian-English machine translation with a tape dictionary, programmed in Algol 60 and published 1961; the year 1959 from the Wikipedia Quicksort article and its cited sources)
- Bodleian Libraries, Ada Lovelace (blogs.bodleian.ox.ac.uk/adalovelace: 1843 paper, Note G Bernoulli numbers)
- Fourmilab text of Menabrea's Sketch with Lovelace's notes (fourmilab.ch/babbage/sketch.html)
- Seven Bridges of Konigsberg (Euler 1736) and Tower of Hanoi (Edouard Lucas, 1883) checked against the Wikipedia articles and their cited sources.
Проверено: 2026-10-17