edubun
Русский

Поиск: линейный и двоичный

Чтобы найти одно имя в отсортированном списке из миллиона, двоичному поиску никогда не нужно больше 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;
}
Загрузка

Перетащи, чтобы повернуть. Колёсико или щипок для масштаба.

Отсортированный список из 31 числа. Выбери искомое значение, затем пройди по шагам двоичный и линейный поиск и сравни число проверок.

Сколько проверок?

Каждая проверка двоичного поиска как минимум вдвое уменьшает оставшуюся часть. Если начать с n значений, после одной проверки останется не больше примерно n/2, после двух примерно n/4, и так далее, пока не останется одно значение. Число делений пополам, нужных, чтобы дойти от n до 1, равно двоичному логарифму n, его записывают log₂ n. Если учесть и последнюю проверку, двоичному поиску нужно не больше ⌊log₂ n⌋ + 1 проверок, где ⌊ ⌋ означает округление вниз. Для 100 значений это 7 проверок, поэтому в «угадай число» всегда можно выиграть за 7 попыток. Для 1000 значений это 10, для миллиона 20, а для миллиарда 30.

Размер списка nЛинейный поиск, худший случайДвоичный поиск, худший случай
773
1001007
1,0001,00010
1,000,0001,000,00020
1,000,000,0001,000,000,00030

Подвох: список должен быть отсортирован

Двоичный поиск работает только потому, что в отсортированном списке одно сравнение говорит, с какой стороны должно быть искомое значение. В неотсортированном списке среднее значение ничего не говорит о соседях, и ты возвращаешься к линейному поиску. Сортировка сама требует работы (о ней следующие два урока), поэтому выбор зависит от применения. Если ищешь в списке один раз, линейный поиск может оказаться самым дешёвым. Если ищешь в одном и том же списке тысячи раз, один раз отсортировать его, а потом пользоваться двоичным поиском быстро окупается. Этот обмен между подготовкой данных и ответами на вопросы о них проходит через весь предмет.

Классическая ошибка в двоичном поиске сидит в шаге обновления. Если написать lo = mid вместо lo = mid + 1, цикл может работать вечно, когда осталось два значения, потому что mid не меняется. Среднее значение уже проверено, поэтому его можно исключить из следующего диапазона.
линейный поискПроверять значения по одному с начала
двоичный поискПроверять середину отсортированного диапазона и отбрасывать половину, где искомого быть не может
худший случайВходные данные, на которых алгоритм делает больше всего работы
log₂ nСколько раз n можно поделить пополам, прежде чем получится 1
lo и hiДва конца диапазона, где искомое значение ещё может быть
-1Обычное возвращаемое значение, которое значит «не найдено»
  1. Сколько проверок самое большее нужно двоичному поиску для отсортированного списка из 1000 значений?
  2. Сколько проверок самое большее нужно линейному поиску для списка из 1000 значений?
  3. Для двоичного поиска список должен быть
    • отсортирован
    • коротким
    • из целых чисел
    • без повторяющихся значений
  4. Сколько проверок сделает двоичный поиск, чтобы найти 23 в списке [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]?
  5. Сколько проверок сделает линейный поиск, чтобы найти 23 в том же списке?
  6. Двоичный поиск ищет 25 и проверяет среднее значение 40. Что дальше?
    • hi переходит прямо перед серединой
    • lo переходит прямо за середину
    • поиск останавливается: 25 нет
    • проверяется следующее значение справа
  7. Ты угадываешь число от 1 до 100, и тебе говорят «больше» или «меньше». Сколько попыток при лучшей стратегии всегда хватает?
  8. Список стал вдвое длиннее. Что происходит с числом проверок двоичного поиска в худшем случае?
    • Оно удваивается
    • Оно растёт примерно на одну
    • Оно остаётся точно таким же
    • Оно возводится в квадрат
  9. Тебе нужно один раз найти одно значение в неотсортированном списке. Что дешевле?
    • Линейный поиск
    • Отсортировать список, затем двоичный поиск
  10. Почему из-за lo = mid (вместо mid + 1) двоичный поиск может зациклиться?
    • Когда осталось два значения, mid может каждый круг оставаться прежним
    • mid становится отрицательным
    • JavaScript этого не разрешает
    • Список становится длиннее
  11. Сколько проверок самое большее нужно двоичному поиску для 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