} else {
// Значение не найдено
}
При работе с сортированными интервалами существуют и другие варианты, и им определенно стоит отдать предпочтение. Алгоритмы count и find работают с линейной сложностью, тогда как алгоритмы поиска в сортированных интервалах (binary_search, lower_bound, upper_bound и equal_range) обладают логарифмической сложностью.
Переход от несортированных интервалов к сортированным влечет за собой изменение критерия сравнения двух величин. Различия между критериями подробно описаны в совете 19, поэтому я не стану повторяться и замечу, что алгоритмы count и find используют критерий равенства, а алгоритмы binary_search, lower_bound, upper_bound и equal range основаны на критерии эквивалентности.
Присутствие величины в сортированном интервале проверяется алгоритмом binary_search. В отличие от функции bsearch из стандартной библиотеки С (а значит, и стандартной библиотеки С++), алгоритм binary_search возвращает только bool. Алгоритм отвечает на вопрос: «Присутствует ли заданное значение в интервале?», и возможны только два ответа: «да» и «нет». Если вам понадобится дополнительная информация, выберите другой алгоритм.
Пример применения binary_search к сортированному вектору (преимущества сортированных векторов описаны в совете 23):
vector<Widget> vw;
sort (vw. Begin(),vw.end());
// Создать вектор, заполнить // данными и отсортировать
Widget w:// Искомое значение
if(binary_search(vw.begin().vw.end(),w)) {
// Значение w присутствует в vw
} else {
// Значение не найдено
}
Если у вас имеется сортированный интервал и вы ищете ответ на вопрос: «Присутствует ли заданное значение в интервале, и если присутствует — то где именно?», следует использовать алгоритм equal_range, хотя на первый взгляд кажется, что вам нужен алгоритм lower_bound. Вскоре мы вернемся к equal_range, а пока проанализируем поиск в интервалах с применением алгоритма lower_bound.
При поиске заданной величины в интервале алгоритм lower_bound возвращает итератор, указывающий на первый экземпляр этой величины (в случае успешного поиска) или на правильную позицию вставки (в случае неудачи). Таким образом, алгоритм lower_bound отвечает на вопрос: «Присутствует ли заданное значение в интервале? Если присутствует, то где находится первый экземпляр, а если нет — где он должен находиться?». Как и в случае с алгоритмом find, результат lower_ bound необходимо дополнительно проверить и убедиться в том, что он указывает на искомое значение. Но в отличие от find, его нельзя просто сравнить с конечным итератором. Вместо этого приходится брать объект, идентифицированный алгоритмом lower_bound, и проверять, содержит ли он искомое значение.
Многие программисты используют lower_bound примерно так:
vector<Widget>::iterator =lower_bound(vw,begin().vw.end(),w):
if (i!=vw.end()&&*i=w){// Убедиться в том, что i указывает
// на объект, и этот объект имеет искомое
// значение. Ошибка!!!!
// Значение найдено, i указывает на первый
// экземпляр объекта с этим значением
} else {
// Значение не найдено
}
В большинстве случаев такая конструкция работает, но в действительности она содержит ошибку. Присмотритесь к проверяемому условию:
if (i!=vw.end()&&*i=w){
В этом условии проверяется
Чтобы исправить ошибку, необходимо убедиться в том, что итератор, полученный от lower__bound, указывает на объект со значением, эквивалентным искомому. Проверку можно выполнить вручную (в совете 19 показано, как это делается, а в совете 24 приведен пример ситуации, когда такое решение оправданно), однако сделать это непросто, поскольку при этом должна использоваться та же функция сравнения, как и при вызове lower_bound. В общем случае мы имеем дело с произвольной
функцией (или объектом функции). При передаче lower_bound функции сравнения эта же функция должна использоваться и в «ручной» проверке эквивалентности; следовательно, при изменении функции сравнения, передаваемой lower_rbound, вам придется внести соответствующие изменения в проверку эквивалентности. В принципе, синхронизировать функции сравнения не так уж сложно, но об этом необходимо помнить, а при программировании хлопот и без этого хватает.
Существует простое решение: воспользуйтесь алгоритмом equal_range. Алгоритм возвращает
Относительно возвращаемого значения equal_range необходимо сделать два важных замечания. Если два итератора совпадают, это говорит о том, что интервал пуст, а значение не найдено. По этому факту можно судить о том, было ли найдено совпадение. Пример:
vector<Widget> vw;
sort (vw.begin(), v.end());
typedef vector<Widget>::iterator VWIter; // Вспомогательные
typedef pair<VWIter,VWIter> VWIterPair: // определения типов
VWIterPar p = equal_range(vw.begin(),vw.end(),w);
if (p.first != p.second){// Если equal_range возвращает
// непустой интервал...
// Значение найдено, p.first
// указывает на первый элемент
// интервала, а p.second -
// на позицию за последним элементом
} else {
// Значение не найдено, p.first
// и p.second указывают на точку
// вставки искомого значения
}
В этом фрагменте используется только критерий эквивалентности, поэтому он всегда верен.
Другая особенность возвращаемого значения equal_range заключается в том, что расстояние между двумя итераторами равно количеству объектов в интервале, то есть количеству объектов, эквивалентных искомому объекту. В результате equal_range не только выполняет функции find для сортированных интервалов, но и заменяет count. Например, поиск в vw объектов Widget, эквивалентных w, с последующим выводом их количества может выполняться следующим образом:
VWIterPair р = equal_range(vw.begin(),vw.end(),w);
cout « 'There are ' « distance(p.first,p.second)
« ' elements in vw equivalent to w.';
До настоящего момента предполагалось, что в интервале ищется некоторое значение, но есть ситуации, в которых возникает задача поиска
class Timestamp {...};
bool operator<(const Timestamp& lhs. //Проверяет, предшествует ли
const Timestamp& rhs); // объект lhs объекту rhs по времени
vector<Timestamp> vt;// Создать вектор, заполнить данными
// и отсортировать так, чтобы
sort(vt.begin(),vt.end()); // 'старые' объекты предшествовали 'новым'
Предположим, из vt требуется удалить все объекты, предшествующие некоторому пороговому объекту ageLimit. В данном случае не нужно искать в vt объект Timestamp, эквивалентный ageLimit, поскольку объекта с точно совпадающим значением может и не быть. Вместо этого в vt ищется граничная позиция, то есть первый элемент, не старший ageLimit. Задача решается элементарно, поскольку алгоритм lowebound предоставляет всю необходимую информацию:
Timestamp ageLimit;
vt.erase(vt.begin().lower_bound(vt.begin(),// Удалить из vt все объекты,
vt.end(),// предшествующие значению
ageLimit));// ageLimit
Слегка изменим условия задачи. Допустим, требуется удалить все объекты, предшествующие или равные ageLmt. Для этого необходимо найти первый объект
vt.erase(vt.begin(),upper_bound(vt.begin(). // Удалить из vt все объекты,
vt.end(), // предшествующие или
ageLimit));