Поделиться Поделиться

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

Обобщенные алгоритмы STL разделяются на четыре большие категории в соответствии с их семантикой. Неизменяющие алгоритмы над последовательностями работают с контейнерами без модификации их содержимого, в то время как изменяющие алгоритмы над последовательностями обычно модифицируют содержимое контейнеров, с которыми они имеют дело. Связанные с сортировкой алгоритмы включают алгоритмы сортировки и слияния, алгоритмы бинарного поиска и операции над множествами, работающие с упорядоченными последовательностями. Наконец, имеется небольшой набор обобщенных числовых алгоритмов. Неизменяющие алгоритмы над последовательностями — это алгоритмы, которые непосредственно не модифицируют контейнеры, с которыми работают. Они включают алгоритмы для поиска элементов в последовательностях, проверки равенства и пересчета элементов последовательности. Это алгоритмы f in d , a d ja c e n t_ f in d , co u n t, f o r _ e a c h , m ism atch, e q u a l и

s e a r c h . Алгоритм a d j a c e n t_ f in d выполняет сканирование последовательности в поисках пары смежных одинаковых элементов. Когда такая пара найдена, алгоритм возвращает итератор, указывающий на первый элемент пары. Алгоритм c o u n t представляет собой неизменяющий алгоритм, сканирующий последовательность для подсчета количества элементов, равных определенному значению. Обобщенный алгоритм f o r _ e a c h применяет функцию f к каждому элементу последовательности. Алгоритмы m ism atch и e q u a l используются для сравнения двух диапазонов. Каждый из них получает три параметра-итератора: f i r s t l , l a s t l и f i r s t 2 . Алгоритм e q u a l возвращает tr u e , если элементы в соответствующих позициях f i r s t l + / и f i r s t 2 + i иравны для всех позиций f i r s t l + iиз диапазона [ f i r s t l ; l a s t l ) , и f a ls e в противном случае. Алгоритм m ism atch возвращает пару итераторов (тип p a ir ) ,11 f i r s t l + in f i r s t 2 + i, которые представляют собой первые позиции последовательностей, где найдены неодинаковые элементы. Если неодинаковых элементов в соответствующих позициях не обнаружено, возвращается пара итераторов l a s t l и f i r s t 2 + ( l a s t l - f i r s t l ) . Для двух данных диапазонов обобщенный алгоритм s e a r c h находит первую позицию в первом диапазоне, с которой вторая позиция входит в первую в качестве подпоследовательности. Этот алгоритм обобщает функции поиска подстрок наподобие библиотечной функции С strstr .

Пример 5.4. Иллюстрация применения обобщенного алгоритма f in d i f

иех05-04.срр" 97а =

#include <iostream>

#include <algorithm>

#include <cassert>

#include <vector>

using namespace std;

// Определение типа унарного предиката:

class GreaterThanSO {

public:

bool operator()(int x) const { return x > 50; }

};

int main()

{

cout << "Иллюстрация применения обобщенного "

<< "алгоритма find_if." << endl ;

// Создание вектора со значениями

// 0, 1, 4, 9, 16, . . . , 144:

vector<int> vectorl;

for (int i = 0; i < 13; ++i)

vectorl.push_back(i * i);

vector<int>::iterator where;

where = find_if(vectorl.begin(), vectorl.e nd(),

GreaterThanSO());

assert (*where == 64);

cout << " -- Ok." << endl;

return 0;

}

Пример 5.5. Иллюстрация применения обобщенного алгоритма

adj a c e n t f in d

"ех05-05.срр" 976 =

#include <iostream>

#include <string>

#include <algorithm>

#include <cassert>

#include <functional>

#include <deque>

using namespace std;

int main()

{

cout << "Иллюстрация применения обобщенного "

<< "алгоритма adjacent_find." << endl;

deque<string> player(5);

deque<string>::iterator i;

// Инициализация дека:

player[0] = "Pele";

player[1] = "Platini";

player[2] = "Maradona";

player[3] = "Maradona";

player[4] = "Rossi";

// Поиск первой пары одинаковых последовательных имен:

i = adjacent_find(player.begin(), player.e n d ());

assert (*i == "Maradona" && *(i+l) == "Maradona");

// Поиск первого имени, лексикографически большего,

// чем следующее за ним:

i = adjacent_find(player.begin(), player.e n d (),

greater<string>());

assert (*i == "Platini" && *(i+l) == "Maradona");

cout << " -- Ok." << endl;

return 0;

}

Изменяющие алгоритмы модифицируют содержимое контейнеров, с которыми работают.

Например, алгоритм u n iq u e удаляет все последовательные дублирующиеся элементы из последовательности. Другие алгоритмы из этой категории копируют, заполнят, генерируют, замещают и т.п. элементы последовательных контейнеров, с которыми работают.

Обобщенные алгоритмы сору и co p y _ b ack w ard используются для копирования элементов из одного диапазона в другой. Вызов copy(firstl/ lastl, first2) копирует элементы из диапазона [ f i r s t l ; l a s t l ) в [ f i r s t 2 ; l a s t 2 ) и возвращает итератор la s t2 , где la s t2 == f i r s t 2 + ( l a s t l - f i r s t l ) . Алгоритм работает в прямом направлении, копируя исходные элементы в порядке f i r s t l , f i r s t l + 1, ..., l a s t l - 1 ,

так что целевой диапазон может перекрываться с исходным, если последний не содержит f i r s t 2. Так, например, со р у может использоваться для сдвига диапазона на одну позицию влево, но не вправо. Обратное верно для алгоритма copy_backw ard: c°py_backward(firstl, lastl, last2) который копирует элементы из диапазона [ f i r s t l ; l a s t l ) в диапазон [ f i r s t 2 ; l a s t 2 ) и возвращает f i r s t 2 , где f i r s t 2 == la s t2 - ( l a s t l - f i r s t l ). Он работает в обратном направлении, копируя исходные элементы в порядке l a s t l - 1 , l a s t l - 2 , ..., f i r s t l . Таким образом, копирование выполняется корректно, если исходный диапазон не содержит l a s t 2.

Пример 5.10. Демонстрация использования обобщенных алгоритмов сору И copy backward

"ех05- 1 0 .срр" 104 =

#include <iostream>

#include <cassert>

#include <algorithm>

#include <vector>

#include <string>

#include <iostream>

using namespace std;

int main()

{

cout << "Демонстрация использования обобщенных "

<< "алгоритмов сору и copy_backward." << endl;

string s ("abcdefghihklmnopqrstuvwxyz");

vector<char> vectorl(s.begin(), s .end());

vector<char> vector2(vectorl.size());

// Копирование vectorl в vector2:

copy(vectorl.begin(), vectorl.e nd(),

vector2.begin( ) ) ;

assert (vectorl == vector2);

// Сдвиг содержимого vectorl влево на 4 позиции:

copy(vectorl.begin() + 4, vectorl.e nd(),

vectorl.begin());

assert (string(vectorl.begin(), vectorl.end()) ==

string("efghihklmnopqrstuvwxyzwxyz"));

// Сдвиг его же вправо на 2 позиции:

copy_backward(vectorl.begin(), vectorl.e nd() - 2,

vectorl.e nd());

assert (string(vectorl.begin(), vectorl.end()) ==

string("efefghihklmnopqrstuvwxyzwx"));

cout << " -- Ok." << endl;

return 0 ;

}

Первый пример copy выполняет копирование содержимого v e c to r l в v e c to r 2. Второй пример иллюстрирует копирование перекрывающихся диапазонов, сдвигая содержимое v e c to r l влево на четыре позиции. Обратите внимание, что после копирования первые четыре символа, ab ed , оказываются потеряны, а последние четыре, wxyz, повторяются в конце дважды. Пример co p y _ b ack w ard выполняет сдвиг на две позиции вправо; такой сдвиг нельзя выполнить при помощи алгоритма сору. В этом случае первые два символа, e f , повторяются в начале строки, а последние два, yz, будут утеряны. Алгоритмы f i l l и f i l l _ n помещают копии данного значения во все позиции диапазона. Вызов fill(first, last, value) помещает l a s t - f i r s t копий v a lu e в [ f i r s t ; l a s t ). Вызов fill_n(first, n, value) помещает n копий v a lu e в [ f i r s t ; f i r s t + n ) . Алгоритм g e n e r a te заполняет диапазон [ f i r s t ; l a s t ) значениями, которые возвращают l a s t - f i r s t последовательных вызовов функции g en (третий параметр алгоритма g e n e r a te ) . Предполагается, что g en не получает никаких аргументов. Обобщенный алгоритм ran d o m _ sh u f f le случайным образом переставляет элементы из диапазона [ f i r s t ; l a s t ), используя для этого функцию, генерирующую псевдослучайные числа. Перестановки, получающиеся при применении алгоритма ran d o m _ sh u f f le , имеют приблизительно равномерное распределение, т.е. вероятность каждой из N\ перестановок диапазона размером N приблизительно равна \/N\ . Обобщенный алгоритм remove удаляет из диапазона те элементы, которые равны определенному значению. Этот алгоритм устойчивый, т.е. он сохраняет относительный порядок остающихся элементов последовательности. Обобщенный алгоритм r e p la c e заменяет элементы в диапазоне, равные некоторому заданному значению, другим значением. Обобщенный алгоритм r o t a t e выполняет циклический сдвиг диапазона. Вызов r o t a t e (f i r s t , m iddle, la s t) циклически сдвигает элементы в диапазоне [ f i r s t ; l a s t ) влево на m id d le - f i r s t позиций. STL предоставляет три обобщенных алгоритма сортировки,— s o r t, p a r t i a l s o r t и s ta b le _ s o r t. Каждый сортирует последовательность с произвольным доступом и размещает результат в том же контейнере, с которым работает. Алгоритм p a r tia l_ _ s o r t требует константного количества дополнительной памяти, s o r t — логарифмического. Они оба по сути являются алгоритмами, работающими “на месте”, в то время как алгоритм s ta b le _ s o r t может потребовать линейного количества дополнительной памяти. В STL входят четыре обобщенных числовых алгоритма: a c c u m u la te , p a r tia l_ s u m , a d ja c e n t_ d if f e r e n c e и in n e r _ j? r o d u c t. В этом разделе мы рассмотрим примеры ис­

пользования каждого из этих алгоритмов. Обобщенный алгоритм a c c u m u la te суммирует значения из указанного диапазона.

49. Функциональные объекты STL. Простые функциональные объекты. Стандартные функциональные объекты. Связыватели std::bind.

Функциональный объект представляет собой любую сущность, которая может быть применена к нулю или большему количеству элементов для получения значения и/или изменения состояния вычислений. В программировании на C++ любая обычная функция отвечает этому определению, но ему же отвечает и объект любого класса (или структуры), который перегружает оператор вызова функции o p e r a to r ().Приведем пример работы функциональных объектов с двумя аргументами функции. Определим шаблонный класс PairSelect, содержащий функцию печати, которая выводит меньший элемент пары в соответствии с определенным нами отношением «меньше чем», задаваемым параметром шаблона. Также в виде параметра шаблона мы зададим тип элементов пары. Следующая программа использует два отношения упорядочения. Они реализованы в виде бинарных предикатов, то есть функций, которые имеют два аргумента и возвращают логическое значение. Наш первый бинарный предикат, LessThan, является шаблоном, поэтому он применим к любому типу, для которого определен оператор < Второй предикат, CompareLastDigits,является обычным, не шаблонным, функциональным классом, который практически совпадает с функциональным классом, определенным в последнем разделепредыдущей главы, только результат его вызова равен true,если последняя цифра первого аргумента меньше, чем у второго, и false - в противном случае.

// funobj3.cpp: Функция operator!) как

// бинарный предикат.

#include <iostream.h>

template <class T>

struct LessThan {

bool operator()(const T &x, const T &y)const

{ return x < y;

}

} ;

struct CompareLastDigits {

bool operator()(int x, int y)const

{ return x % 10 < у % 10;

}

};

Функциональные объекты 141

template <class Т, class Compare>

class PairSelect {

public:

PairSelect(const T &x, const T &y) : a(x), b(y){}

void PrintSmaller()const

{ cout << (Compare()(a, b) ? a : b) « endl;

}

private:

Т а , b ;

} ;

int main()

{ PairSelect<double, LessThan<double> > P (123.4, 98.7);

P.PrintSmaller(); // Вывод: 98.7

PairSelect<int, CompareLastDigits> Q (123, 98);

Q.PrintSmaller(); // Вывод: 123

return 0;

}

Эта программа сначала выводит значение 98.7, потому что оно меньше другого элемента объекта Q - 123.4. После этого она выводит 123, поскольку последняя цифра этого числа - 3 - меньше, чем последняя цифра числа 98. Одно из главных преимуществ заключается в том, что объекты, в отличие от обычных функций, могут хранить дополнительную информацию, которая затем может использоваться обобщенными алгоритмами или контейнерами. Связыватель (binder) применяется для преобразования бинарного функционального объекта в унарный путем связывания аргумента с некоторым определенным значением. В STL это достигается с помощью использования привяжи,которая является одним из видов адаптера функции. Чтобы превратить бинарный предикат в унарный, привязав его второй аргумент, мы используем привязку bind2nd.В нашем примере требуется использовать выражение bind2nd(less<int>(), 100) чтобы указать, что мы хотим считать только значения, меньшие 100. Следующая программа показывает, как это все работает // binder.срр: Использование адаптера bind2nd для подсчета, сколько из элементов массива меньше, чем 100.

♦include <iostream>

♦include <algorithm>

♦include <functional>

using namespace std;

int main()

{ int a [10] = {800, 3, 4, 600, 5, 6, 800, 71, 100, 2},

n = 0;

n = count_if(a, a + 10, bind2nd(less<int>(), 100));

cout << n « endl; // Вывод: 6

return 0;

}

Для п р и вязы ван и я первого аргумента сущ ествует п р и вязка bindXst. К примеру, заменим условие х < 100 эквивалентным условием 100 > х Этого можно добиться, привязав первый операнд у выражения у > х к значению 100. Следовательно, программа binder.срр выдаст тот же результат, если мы заменим вызов c o u n t if на следующий: n = count_if(a, а + 10, bindlst(greater<int>(), 100));

← Предыдущая страница | Следующая страница →