Гид по технологиям

Итеративный бинарный поиск на C, C++ и Python

6 min read Алгоритмы Обновлено 21 Dec 2025
Итеративный бинарный поиск на C, C++ и Python
Итеративный бинарный поиск на C, C++ и Python

Кратко

Итеративный бинарный поиск быстро находит элемент в отсортированном массиве за O(log n) сравнений и использует O(1) дополнительной памяти в итеративной реализации. В этой статье вы найдёте понятные объяснения, рабочие реализации на C, C++ и Python, тест-кейсы, когда метод не применим, альтернативы и практические советы по отладке.

О чём эта статья

  • Понятие бинарного поиска и логика работы.
  • Полные примеры кода на C, C++ и Python (итеративный вариант).
  • Когда и почему итеративный вариант экономит память.
  • Частые ошибки, тест-кейсы и чек-листы для интервью/инженеров.

C Programming Console

Что такое бинарный поиск?

Бинарный поиск — это алгоритм для поиска позиции элемента в отсортированном массиве. Он последовательно делит диапазон поиска пополам, сравнивая искомое значение с медианой текущего диапазона.

Ключевая идея: если искомое значение больше среднего элемента, то искать нужно только в правой половине; если меньше — в левой. При каждом шаге размер диапазона уменьшается примерно вдвое, поэтому время работы составляет O(log n).

Кратко про ограничения и предпосылки:

  • Массив должен быть строго или нестрого отсортирован по возрастанию (или по убыванию — требуется соответствующая адаптация сравнения).
  • Для корректной работы нужны индексируемые структуры (массивы, списки с доступом по индексу).
  • При наличии дубликатов бинарный поиск вернёт некоторую позицию совпадения; для поиска первого/последнего вхождения требуются модификации.

Ментальная модель (как думать о бинарном поиске)

  • Представьте, что вы ищете слово в словаре: открываете книгу на середине и решаете, в какую половину дальше смотреть.
  • Каждый шаг — это отсечение половины возможных позиций.
  • Если на каждом шаге вы сокращаете пространство в 2 раза, то число шагов ≈ log2(n).

Почему выбирать итеративную реализацию?

  • Итеративный вариант использует постоянный объем дополнительной памяти: O(1).
  • Рекурсивный вариант использует стек вызовов и потребляет O(log n) памяти (в глубине рекурсии), что может быть критично в ограниченной среде.
  • Итеративная версия проще контролируется для условий выхода и стабильней в ресурсозависимых средах.

Практические замечания

  • Безопасный расчёт mid: используйте mid = low + (high - low) / 2 или mid = low + (high - low) >> 1, чтобы избежать переполнения при больших индексах.
  • Если массив отсортирован по убыванию, инвертируйте сравнения.
  • Для поиска первого/последнего вхождения выбирайте сдвиги границ без немедленного выхода при совпадении.

Итеративная реализация: C (полная программа)

Ниже — рабочий пример на C. Функция binarySearch возвращает индекс найденного элемента или -1, если элемент отсутствует.

#include 

int binarySearch(int arr[], int arr_size, int search_number) {
    int low = 0;
    int high = arr_size - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2; // безопасный расчёт
        if (arr[mid] == search_number)
            return mid;
        else if (arr[mid] > search_number)
            high = mid - 1;
        else
            low = mid + 1;
    }
    return -1;
}

int main() {
    int arr[100], i, arr_size, search_number;
    printf("Enter number of elements: ");
    if (scanf("%d", &arr_size) != 1) return 1;
    if (arr_size < 0 || arr_size > 100) return 1;

    printf("Please enter numbers: ");
    for (i = 0; i < arr_size; i++) {
        if (scanf("%d", &arr[i]) != 1) return 1;
    }

    printf("Enter number to be searched: ");
    if (scanf("%d", &search_number) != 1) return 1;

    i = binarySearch(arr, arr_size, search_number);
    if (i == -1)
        printf("Number not present\n");
    else
        printf("Number is present at position %d\n", i + 1);

    return 0;
}

Важно: код предполагает, что вводимые элементы уже отсортированы. Если это не так, перед поиском массив нужно отсортировать.

C++: минимальные отличия и советы

В C++ удобно использовать iostream и контейнер std::vector. Пример замены scanf/printf на cin/cout:

#include 
#include 
using namespace std;

int binarySearch(const vector& arr, int search_number) {
    int low = 0, high = (int)arr.size() - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (arr[mid] == search_number) return mid;
        else if (arr[mid] > search_number) high = mid - 1;
        else low = mid + 1;
    }
    return -1;
}

int main() {
    int arr_size, search_number;
    cout << "Enter number of elements: ";
    if (!(cin >> arr_size)) return 1;
    vector arr(arr_size);
    cout << "Please enter numbers: ";
    for (int i = 0; i < arr_size; ++i) cin >> arr[i];
    cout << "Enter number to be searched: ";
    cin >> search_number;
    int result = binarySearch(arr, search_number);
    if (result == -1) cout << "Number not present\n";
    else cout << "Number is present at position " << (result + 1) << "\n";
}

Совет: используйте стандартные алгоритмы (std::binary_search / std::lower_bound / std::upper_bound) в продукционных задачах — они проверены и оптимизированы.

Python: реализация и ввод

Python не имеет примитивных массивов, но списки (list) обеспечивают доступ по индексу. Ниже — итеративная функция и пример чтения с ввода:

def binarySearch(arr, arr_size, search_number):
    low = 0
    high = arr_size - 1
    while low <= high:
        mid = low + (high - low) // 2  # floor division
        if arr[mid] == search_number:
            return mid
        elif arr[mid] > search_number:
            high = mid - 1
        else:
            low = mid + 1
    return -1

if __name__ == '__main__':
    arr_size = int(input("Enter number of elements: "))
    arr = []
    print("Please enter numbers: ")
    for _ in range(arr_size):
        arr.append(int(input()))
    search_number = int(input("Enter number to be searched: "))
    result = binarySearch(arr, arr_size, search_number)
    if result == -1:
        print("Number not present")
    else:
        print("Number is present at position", result + 1)

Примечание: в реальных задачах для пакетного ввода удобнее читать строку с числами и парсить через split(), особенно при больших объёмах данных.

Diagrammatic representation of Binary Search Algorithm

Примеры вывода

Output of Binary Search when element is found

Output of Binary Search when element is not found

Когда бинарный поиск не применим (контрпримеры)

  • Массив не отсортирован — бинарный поиск даст неверный результат.
  • Динамические структуры без произвольного доступа по индексу (например, связный список) — доступ к среднему элементу не O(1), поэтому выгода теряется.
  • Очень маленькие массивы — для n малых константных значений линейный поиск может быть проще и быстрее в практике.
  • Если нужно найти все вхождения значения, стандартный бинарный поиск вернёт какое‑то одно вхождение; для первого/последнего требуется вариация поиска границ.

Альтернативные подходы

  • Интерполяционный поиск — эффективен на равномерно распределённых данных, в среднем быстрее, но в худшем — O(n).
  • Прыжковый поиск (jump search) — полезен при упорядоченных массивах и медленном доступе к памяти.
  • Хэш-таблицы — для поиска наличия элемента за O(1) амортизированно, но требуют дополнительной памяти и не дают информацию о позиции в исходном массиве.

Частые ошибки и ошибки новичков

  • Неправильный расчёт mid как (low + high) / 2 без защиты от переполнения при больших индексах.
  • Забвение проверки граничных условий (low <= high).
  • Ожидание, что бинарный поиск найдёт «первое» вхождение при наличии дубликатов.
  • Использование бинарного поиска на неотсортированных данных.

Тест-кейсы и критерии приёмки

Критерии приёмки (минимум):

  • Функция возвращает индекс найденного элемента или -1 при отсутствии.
  • Работает корректно на краевых случаях: пустой массив, массив из 1 элемента, элемент в начале/в конце, все элементы равны.
  • Не вызывает переполнения при больших размерах массива.

Примеры тестов:

  1. Пустой массив: [] -> любое искомое значение -> -1
  2. Один элемент: [5], искать 5 -> 0
  3. Элемент в начале: [1,2,3], искать 1 -> 0
  4. Элемент в конце: [1,2,3], искать 3 -> 2
  5. Отсутствие: [1,3,5,7], искать 4 -> -1
  6. Дубликаты: [1,2,2,2,3], искать 2 -> вернуть индекс в диапазоне [1,3] (или модифицировать для первого/последнего)

Чек-лист по ролям

Интервьюер:

  • Убедиться, что кандидат учитывает границы и переполнение.
  • Попросить объяснить поведение при дубликатах.

Кандидат (студент/соискатель):

  • Продемонстрировать итеративную реализацию с безопасным mid.
  • Обсудить временную и пространственную сложность.

Инженер (производственный код):

  • Рассмотреть использование std::lower_bound / std::upper_bound (C++).
  • Добавить валидацию входных данных и обработку ошибок ввода.

Советы по отладке и производительности

  • Логируйте low/high/mid при отладке на небольших массивах, чтобы увидеть, как сужается диапазон.
  • Для больших массивов используйте профилирование, чтобы проверить, не является ли узким местом ввод/вывод, а не сам поиск.
  • В критичных по памяти системах отдавайте предпочтение итеративной версии.

Факты и числа

  • Время работы: O(log n) в среднем и в худшем случае для упорядоченного массива.
  • Пространство: O(1) для итеративной реализации, O(log n) стек вызовов для рекурсивной.
  • Поддержка больших n: бережно рассчитывайте mid, чтобы избежать переполнения.

Модификации для особых задач

  • Первое вхождение: при совпадении смещайте правую границу, но сохраняйте позицию и продолжайте искать в левой части.
  • Последнее вхождение: аналогично, но двигайте левую границу при совпадении.
  • Поиск на убывающем массиве: меняйте направления сравнений (arr[mid] < search_number => high = mid - 1).

Пример изменённого поиска первого вхождения (Python)

def first_occurrence(arr):
    low, high = 0, len(arr) - 1
    result = -1
    while low <= high:
        mid = low + (high - low) // 2
        if arr[mid] == target:
            result = mid
            high = mid - 1  # ищем дальше в левой части
        elif arr[mid] > target:
            high = mid - 1
        else:
            low = mid + 1
    return result

Безопасность и приватность

Алгоритм сам по себе не обрабатывает конфиденциальные данные. В системах, где ввод поступает извне, проверяйте и фильтруйте ввод, чтобы предотвратить сбои из-за некорректных данных или переполнений.

Резюме

Итеративный бинарный поиск — это эффективный и экономный по памяти алгоритм для поиска в отсортированных массивах. Он обязателен к изучению для интервью и полезен в реальной практике. Используйте безопасный расчёт mid, учитывайте дубликаты и ограничивающие условия, выбирайте рекурсивный или итеративный вариант с учётом доступной памяти.

Важно:

  • Всегда предполагаем, что вход отсортирован.
  • Для производственного кода предпочитайте проверенные библиотеки, если они доступны.

Краткие ключевые выводы:

  • Бинарный поиск: O(log n) по времени.
  • Итеративная версия: O(1) по памяти.
  • Требуется отсортированный массив; для дубликатов нужны модификации.

Спасибо за чтение! Надеемся, примеры кода и чек-листы помогут вам быстро реализовать и протестировать бинарный поиск в вашей среде.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как пользоваться поисковыми операторами Google
Поисковая оптимизация

Как пользоваться поисковыми операторами Google

Как изменить поисковик в Chrome
браузер

Как изменить поисковик в Chrome

Лучшие Android-приложения для видеочата
Мобильные приложения

Лучшие Android-приложения для видеочата

Как пользоваться флеш‑накопителем в Windows 10
How-to

Как пользоваться флеш‑накопителем в Windows 10

Доступ к Unstoppable Domains в браузере
Web3

Доступ к Unstoppable Domains в браузере

Cortana + Power BI: голосовой доступ к данным
Аналитика

Cortana + Power BI: голосовой доступ к данным