В std::map нужно найти элемент по ключу: почему std::find по диапазону принципиально хуже map::find?
map::find использует упорядоченную структуру самого контейнера и выполняет поиск за логарифмическое время. Универсальный алгоритм std::find последовательно проверяет элементы диапазона, поэтому работает за линейное время. Для поиска по ключу в std::map следует применять член-контейнерный метод find.
STL разделяет контейнеры, итераторы и алгоритмы: универсальные алгоритмы работают с диапазонами через итераторы, а контейнеры могут предоставлять специализированные операции. Такой подход позволяет использовать один алгоритм с разными структурами данных, но универсальный алгоритм не обязан знать внутренние свойства конкретного контейнера.
Методы ассоциативных контейнеров появились для операций, которые используют их ключевую организацию. Поэтому std::map::find может задействовать порядок ключей, тогда как std::find видит только последовательность элементов диапазона.
Если применить std::find к диапазону std::map, алгоритм будет перебирать элементы один за другим, пока не найдёт нужный или не достигнет конца диапазона. В контейнере из n элементов это требует в худшем случае O(n) сравнений.
У map::find сложность поиска гарантированно логарифмическая — O(log n). При больших контейнерах выбор общего алгоритма вместо специализированного метода может существенно увеличить задержку, особенно если поиск выполняется часто.
std::map хранит элементы в порядке, определяемом компаратором ключей. Его find использует этот порядок: на каждом шаге отбрасывается часть возможных позиций, пока не будет найден эквивалентный ключ либо установлено, что такого ключа нет. Стандарт гарантирует логарифмическую сложность поиска, хотя конкретная внутренняя структура не обязана быть раскрыта как конкретный вид дерева.
std::find — это универсальный алгоритм последовательного поиска. Он не знает, что элементы имеют ключи, не использует сортировку и не может произвольно пропускать части диапазона: каждый следующий элемент проверяется по очереди. Его сложность для диапазона длины n составляет O(n).
Различается также интерфейс результата. Оба варианта возвращают итератор, но map::find принимает ключ и использует компаратор контейнера, а std::find сравнивает элементы диапазона с заданным значением. Для std::map элементом является пара ключ–значение, поэтому применение std::find обычно требует дополнительного условия, проверяющего ключ.
Здесь поиск выполняется специализированным методом по ключу. Если требуется найти элемент по значению, а не по ключу, map::find не подходит: придётся просматривать элементы линейно либо поддерживать дополнительную структуру индекса.
Следует отличать map::find от std::lower_bound по диапазону итераторов map. Для произвольного доступа алгоритм может делить диапазон по индексам, но итераторы std::map не являются итераторами произвольного доступа. Поэтому такой универсальный алгоритм не получает логарифмическое время по перемещениям итератора; специализированный метод контейнера предпочтительнее.
Сервис хранит несколько миллионов записей в std::map и выполняет поиск записи по идентификатору для каждого входящего запроса. Рассматривались два варианта: использовать std::find, проверяющий ключ каждого элемента, или вызвать map::find.
Первый вариант прост с точки зрения универсального алгоритма, но имеет линейную сложность и плохо масштабируется. Второй использует упорядоченность контейнера и имеет логарифмическую сложность, поэтому был выбран. Если порядок ключей не нужен, а важнее ожидаемый быстрый поиск, дополнительно можно рассмотреть std::unordered_map, но это уже другой контейнер с другими свойствами порядка, требованиями к хешированию и поведением в неблагоприятных случаях.
В результате для поиска по ключу остался map::find, а линейный обход применяется только для задач, которые действительно нельзя выразить через ключевой поиск.
std::find на std::map, если нужен поиск по ключу?Да, это корректно при правильно заданном сравнении элемента с искомым значением или при использовании подходящего предиката. Проблема не в корректности, а в сложности: алгоритм не использует упорядоченность map и в худшем случае проверяет все элементы.
map::find на std::lower_bound по диапазону map без потери эффективности?Нет, универсальный std::lower_bound не гарантирует такую же эффективность для итераторов std::map. Хотя диапазон упорядочен, итераторы map являются двунаправленными, а продвижение к середине диапазона требует последовательного перемещения. Член-контейнерная операция знает структуру map и обеспечивает требуемую логарифмическую сложность поиска.
map?Обычный map::find не решает такую задачу, поскольку индекс контейнера построен по ключам. Просмотр значения требует линейного обхода, то есть O(n). Если поиск по значению частый, обычно создают дополнительный индекс, используют контейнер, ориентированный на нужный критерий, либо выбирают другую модель данных; это увеличивает расход памяти и усложняет синхронизацию структур.