Лекция-2.10.ppt
- Количество слайдов: 36
C++ standard library Algorithms Более 100 функций Лекция 17 -21. 04. 2014 г. 1
В STL реализовано большое количество стандартных алгоритмов, предназначенных для различной обработки элементов коллекций: поиска, сортировки, копирования, переупорядочения, модификации, численных расчетов. Алгоритмы не являются методами контейнерных классов; это – внешние (глобальные) шаблонные функции, работающие с итераторами. У такого подхода есть важное достоинство – вместо того, чтобы реализовывать каждый алгоритм для каждого типа контейнера, достаточно реализовать его один раз для обобщенного типа контейнера. Для использования алгоритмов в некоторой программе к ней необходимо подключить заголовочный файл <algorithm>. Может также потребоваться подключение файлов <functional> и <numeric>. Лекция 17 -21. 04. 2014 г. 2
Интервалы Любой алгоритм работает с одним или несколькими интервалами. В частном случае интервал может содержать и весь контейнер. Как правило, интервал задается для функции двумя аргументами – итераторами (начало и конец интервала). Корректность задания этих границ интервала должна обеспечивать вызывающая сторона (клиент). Возможные ошибки (итераторы относятся к разным контейнерам, итератор начала находится после итератора конца и пр. ) могут привести к непредсказуемым последствиям (например, к зацикливанию или нарушению защиты памяти). Все алгоритмы работают с полуоткрытыми интервалами: интервал включает заданную начальную позицию, но конечная позиция в него не включается. Лекция 17 -21. 04. 2014 г. 3
Использование нескольких интервалов Для выполнения некоторых алгоритмов требуется сразу несколько интервалов. В таких случаях обычно необходимо задать начало и конец только одного интервала, а для других – задается только начало. Их конечная позиция определяется по количеству элементов в первом интервале. Например: if( equal(col 1. begin(), col 1. end(), col 2. begin() ) {…} Лекция 17 -21. 04. 2014 г. 4
Передача функций в качестве аргументов алгоритмов Некоторые алгоритмы допускают использование внешних функций, которые вызываются в процессе внутренней работы алгоритма. Такая возможность делает алгоритмы еще более гибкими и мощными. Например, алгоритм accumulate() по умолчанию вычисляет сумму элементов. А если переключиться с оператора + на оператор *, то алгоритм вычислит произведение элементов. Лекция 17 -21. 04. 2014 г. 5
Передача функции в качестве параметра // accumulate example #include <iostream> // cout #include <functional> // minus #include <numeric> // accumulate using namespace std; int myfunction (int x, int y) {return x+2*y; } struct myclass { int operator()(int x, int y) {return x+3*y; } }; int main () { int init = 100; // Начальное значение int numbers[] = {10, 20, 30}; cout << "using default accumulate: "; cout << accumulate(numbers, numbers+3, init); cout << 'n'; cout << "using functional's minus: "; cout << accumulate (numbers, numbers+3, init, minus<int>()); cout << 'n'; cout << "using custom function: "; cout << accumulate (numbers, numbers+3, init, myfunction); cout << 'n'; cout << "using custom object: "; cout << accumulate (numbers, numbers+3, init, myclass()); cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 6
Предикаты Особую разновидность функций, используемых для работы алгоритмов, составляют предикаты. Это – функции, возвращающие логическое значение (bool). С их помощью часто определяют критерии сортировки и поиска. В зависимости от количества используемых аргументов предикаты делятся на унарные и бинарные. Унарные предикаты проверяют некоторое свойство единственного своего аргумента, например, является ли число простым. Кроме того, унарный предикат может использоваться как критерий, определяющий, к каким элементам контейнера должна применяться операция. Бинарные предикаты обычно сравнивают некоторое свойство двух аргументов. Часто такая возможность используется для выполнения сортировки, где требуется проверять, в каком соотношении находятся два соседних элемента контейнера. Лекция 17 -21. 04. 2014 г. 7
Предикаты #include <iostream> #include <list> #include <algorithm> #include <cstdlib> using namespace std; bool is. Prime(int n) { // унарный предикат n = abs(n); if( n == 0 || n == 1 ) return false; int d; for( d = n/2; n%d != 0; --d ) {} return d == 1; } int main () { list<int> col 1; for( int i = 24; i <= 30; ++i) col 1. push_back(i); auto pos = find_if(col 1. begin(), col 1. end(), is. Prime); if( pos != col 1. end() ) cout << *pos << " is first prime number found" << endl; else cout << " no prime number found" << endl; } Лекция 17 -21. 04. 2014 г. 8
Функциональные объекты (функторы) В алгоритмы можно передавать вместо обычных функций объекты (экземпляры классов), которые ведут себя как функции. Для того, чтобы экземпляр класса был похож на функцию и вел себя как функция, в классе должна быть перегружена унарная операция (). Тогда применение этой операции к объекту будет внешне ничем неотличимо от вызова функции (то есть имя объекта будет восприниматься как функция). Поэтому подобные объекты называются объектамифункциями, функциональными объектами, или функторами. Однако возможности функциональных объектов значительно шире, чем у функций, потому что они являются экземплярами классов, могут иметь сколь угодно сложную «начинку» (поля, методы, конструкторы и пр. ) и находиться в разные моменты времени в разных состояниях. Лекция 17 -21. 04. 2014 г. 9
Стандартные функциональные объекты Стандартная библиотека С++ содержит много шаблонных классов, собранных в заголовочном файле <functional>, позволяющих создавать полезные (универсальные) функциональные объекты. Например, практически для всех унарных и бинарных операций языка С++ определены соответствующие шаблонные классы: • арифметические операции: plus, minus, multiplies, divides, modulus, negate • операции сравнения: equal_to, not_equal_to, greater_equal, less_equal • логические операции: logical_and, logical_not, logical_or • битовые операции: bit_and, bit_or, bit_xor Лекция 17 -21. 04. 2014 г. 10
Пример с шаблонным классом greater<T> // greater example #include <iostream> // cout #include <functional> // greater #include <algorithm> // sort using namespace std; int main () { int numbers[]={20, 40, 50, 10, 30}; sort (numbers, numbers+5, greater<int>()); for (auto x : numbers) cout << x << ' '; cout << 'n'; return 0; } Здесь greater<int>() – это вызов конструктора шаблонного класса. Результат вызова – экземпляр класса, который является функциональным объектом и именно этот объект передается в функцию sort. Лекция 17 -21. 04. 2014 г. 11
Классификация алгоритмов Разные алгоритмы предназначены для решения разных задач, поэтому их можно классифицировать по основным областям применения. Например, одни алгоритмы только читают данные, другие модифицируют их, а третьи изменяют порядок следования элементов в контейнере. Одна из возможных классификаций алгоритмов: 1. Не модифицирующие алгоритмы 2. Модифицирующие алгоритмы 3. Алгоритмы удаления 4. Перестановочные алгоритмы 5. Алгоритмы сортировки 6. Алгоритмы упорядоченных интервалов 7. Численные алгоритмы Лекция 17 -21. 04. 2014 г. 12
Не модифицирующие алгоритмы Эти алгоритмы сохраняют как порядок следования обрабатываемых элементов, так и их значения. Могут вызываться для всех стандартных контейнеров. • for_each() – выполняет операцию с каждым элементом (!) • count() – возвращает количество элементов • count_if() – возвращает количество элементов, удовлетворяющих заданному критерию • min_element() – возвращает элемент с минимальным значением • max_element() – возвращает элемент с максимальным значением • find() – ищет первый элемент с заданным значением • find_if() – ищет первый элемент, удовлетворяющий заданному критерию • search_n() – ищет первые n последовательных элементов с заданными свойствами Лекция 17 -21. 04. 2014 г. 13
Не модифицирующие алгоритмы • search() – ищет первое вхождение подинтервала • find_end() – ищет последнее вхождение подинтервала • find_first_of() – ищет первый из нескольких возможных элементов • adjacent_find() – ищет два смежных элемента, равных по заданному критерию • equal() – проверяет, равны ли два интервала • mismatch() – возвращает первый различающийся элемент в двух интервалах • lexicographical_compare() – проверяет, что один интервал меньше другого по лексикографическому критерию Лекция 17 -21. 04. 2014 г. 14
Не модифицирующие алгоритмы // search_n example #include <iostream> #include <algorithm> #include <vector> using namespace std; bool mypredicate (int i, int j) { return (i!=j); } int main () { int myints[]={10, 20, 30, 20, 10, 20}; vector<int> myvector (myints, myints+8); auto it = myvector. begin(); // using default comparison: it = search_n (myvector. begin(), myvector. end(), 2, 30); if (it!=myvector. end()) cout << "two 30 s found at position " << (it-myvector. begin()) << 'n'; else cout << "match not foundn"; // using predicate comparison: it = search_n (myvector. begin(), myvector. end(), 3, 20, mypredicate); if (it!=myvector. end()) cout << "two 10 s found at position " << int(it-myvector. begin()) << 'n'; else cout << "match not foundn"; return 0; } Лекция 17 -21. 04. 2014 г. 15
Не модифицирующие алгоритмы // adjacent_find example #include <iostream> #include <algorithm> // adjacent_find #include <vector> using namespace std; bool myfunction (int i, int j) { return (i==j); } int main () { vector<int> myvector = {5, 20, 5, 30, 20, 10, 20}; auto it = myvector. begin(); // using default comparison: it = adjacent_find (myvector. begin(), myvector. end()); if (it!=myvector. end()) cout << "the first pair of repeated elements are: " << *it << 'n'; //using predicate comparison: it = adjacent_find (++it, myvector. end(), myfunction); if (it!=myvector. end()) cout << "the second pair of repeated elements are: " << *it << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 16
Модифицирующие алгоритмы Эти алгоритмы изменяют значения элементов контейнера. Модификация производится непосредственно внутри интервала или в процессе копирования в другой интервал (в этом случае исходный интервал остается без изменений). • for_each() – выполняет операцию с каждым элементом • copy() – копирует интервал, начиная с первого элемента • copy_backwards() – копирует интервал, начиная с последнего элемента • transform() – модифицирует (и копирует) элементы; объединяет элементы двух интервалов • merge() – производит слияние двух интервалов • swap_ranges() – меняет местами элементы двух интервалов • fill() – заменяет каждый элемент заданным значением • fill_n() – заменяет n элементов заданным значением • generate() – заменяет каждый элемент результатом операции Лекция 17 -21. 04. 2014 г. 17
Модифицирующие алгоритмы • generate_n() – заменяет n элементов результатом операции • replace() – заменяет элементы с заданным значением другим значением • replace_if() – заменяет элементы, соответствующие критерию, заданным значением • replace_copy() – заменяет элементы с заданным значением при копировании интервала • replace_copy_if() – заменяет элементы, соответствующие критерию, при копировании интервала Лекция 17 -21. 04. 2014 г. 18
Модифицирующие алгоритмы В данной группе центральное место занимают алгоритмы for_each() и transform(), которые используются для модификации элементов последовательности, но работают по-разному. Алгоритму for_each() передается операция, которая модифицирует свой аргумент. Следовательно, аргумент должен передаваться по ссылке: void square(int& x) { x = x * x; } … for_each(col 1. begin(), col 1. end(), square); Алгоритм transform() использует операцию, которая возвращает модифицированный аргумент, а результат прсваивается исходному элементу: int square(int& x) { return x * x; } … transform(col 1. begin(), col 1. end(), col 1. begin(), square); Лекция 17 -21. 04. 2014 г. 19
Модифицирующие алгоритмы // transform algorithm example #include <iostream> // cout #include <algorithm> // transform #include <vector> // vector #include <functional> // plus using namespace std; int square (int x) { return x * x; } int main () { vector<int> a, b; for (int i=1; i<6; i++) a. push_back (i*2); // a: 2 4 6 8 10 b. resize(a. size()); // allocate space transform (a. begin(), a. end(), b. begin(), square); // b: 4 16 36 64 100 transform (a. begin(), a. end(), b. begin(), a. begin(), plus<int>()); // a: 6 20 42 72 110 cout << "a contains: "; for (auto x : a) cout << ' ' << x; cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 20
Модифицирующие алгоритмы // generate algorithm example #include <iostream> // cout #include <algorithm> // generate #include <vector> // vector #include <ctime> // time #include <cstdlib> // rand, srand using namespace std; int Random. Number () { return (rand()%100); } // function generator struct c_pow 2 {// class generator int current; c_pow 2() {current=1; } int operator()() {return current*=2; } }; int main () { srand ( unsigned ( time(0) ) ); vector<int> myvector (8); generate (myvector. begin(), myvector. end(), Random. Number); cout << "myvector contains: "; for (auto x : myvector) cout << x << ' ’; cout << 'n'; generate (myvector. begin(), myvector. end(), c_pow 2()); cout << "myvector contains: "; for (auto x : myvector) cout << x << ' ’; cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 21
Алгоритмы удаления элементов из контейнера Эти алгоритмы предназначены для удаления элементов либо в заданном интервале контейнера, либо в процессе копирования в другой интервал. Как и в случае с модифицирующими алгоритмами, их приемником не может быть ассоциативный контейнер, так как элементы ассоциативного контейнера считаются константными. • remove() – удаляет элементы с заданным значением • remove_if() – производит удаляет элементы по заданному критерию • remove_copy() – копирует элементы, значение которых отлично от заданного • remove_copy_if() – копирует элементы, не соответствующие заданному критерию • unique() – удаляет смежные дубликаты (элементы, равные своему предшественнику) • unique_copy() – копирует элементы с удалением смежных дубликатов Лекция 17 -21. 04. 2014 г. 22
Алгоритмы удаления элементов из контейнера Алгоритмы ограничиваются толко «логическим» удалением элементов, то есть их перезаписью следующими элементами, которые не были удалены. Таким образом, количество элементов в интервалах, с которыми работают эти алгоритмы, остается прежним, а алгоритм возвращает позицию нового «логического конца» интервала. Лекция 17 -21. 04. 2014 г. 23
Перестановочные алгоритмы Эти алгоритмы изменяют порядок следования элементов контейнера (но не их значения) путем присваивания и перестановки значений. Как и в случае с модифицирующими алгоритмами, их приемником не может быть ассоциативный контейнер, так как элементы ассоциативного контейнера считаются константными. reverse() – переставляет элементы в обратном порядке reverse_copy() – копирует элементы, переставленные в обратном порядке rotate() – производит циклический сдвиг элементов rotate_copy() – копирует элементы с циклическим сдвигом next_permutation() – переставляет элемнты prev_permutation() – переставляет элемнты random_shuffle() – переставляет элемнты в случайном порядке partition() – изменяет порядок следования элементов так, что элементы, соответствующие критерию, оказываются спереди stable_partition() – то же, что и partition(), но с сохранением относительного расположения элементов, соответствующих и не соответствующих критерию. Лекция 17 -21. 04. 2014 г. 24
Алгоритмы сортировки Эти алгоритмы являются частным случаем перестановочных алгоритмов, однако сортировка обычно требует больше времени. sort() – сортирует все элементы stable_sort() – сортирует с сохранением порядка следования равных элементов partial_sort() – сортирует до тех пор, пока первые n элементов не будут упорядочены правильно nth_element() – сортирует элементы слева и справа от элемента в позиции n partition() – изменяет порядок следования элементов так, что элементы, соответствующие критерию, оказываются спереди stable_partition() – то же, что и partition(), но с сохранением относительного расположения элементов, соответствующих и не соответствующих критерию make_heap() – преобразует интервал в кучу push_heap() – добавляет элемент в кучу pop_heap() – удаляет элемент из кучи sort_heap() – сортирует кучу (которая после вызова перестает быть кучей Лекция 17 -21. 04. 2014 г. 25
Алгоритмы сортировки часто критичны по времени, поэтому STL содержит несколько алгоритмов, различающихся по способу сортировки или составу сортируемых элементов (полная или частичная сортировка). Любому алгоритму сортировки в необязательном аргументе всегда можно передать критерий сортировки. По умолчанию критерием сортировки является функтор less<>, а элементы сортируются по возрастанию значений. Лекция 17 -21. 04. 2014 г. 26
Алгоритмы для упорядоченных входных интервалов Эти алгоритмы требуют, чтобы интервалы, с которыми они работают, были предварительно упорядочены по соответствующему критерию. Достоинством этих алгоритмов является их невысокая сложность. binary_search() – проверяет, содержит ли интервал заданный элемент includes() – проверяет, что каждый элемент интервала также является элементом другого интервала lower_bound() – находит первый элемент со значением, большим либо равным заданному upper_bound() – находит первый элемент со значением, большим заданному equal_range() – возвращает количество элементов в интервале, равных заданному значению Лекция 17 -21. 04. 2014 г. 27
Алгоритмы для упорядоченных входных интервалов merge() – выполняет слияние двух интервалов set_union() – вычисляет упорядоченное объединение двух интервалов set_intersection() – вычисляет упорядоченное пересечение двух интервалов set_difference() – вычисляет упорядоченный интервал, который содержит все элементы интервала, не входящие в другой интервал set_symmetric_difference() – вычисляет упорядоченный интервал, который содержит все элементы интервала, входящие только в один из двух интервалов inplace_merge() – выполняет слияние “на месте” двух последовательных упорядоченных интервалов Лекция 17 -21. 04. 2014 г. 28
Алгоритмы для упорядоченных входных интервалов // lower_bound/upper_bound example #include <iostream> #include <algorithm> // lower_bound, upper_bound, sort #include <vector> using namespace std; int main () { vector<int> v({10, 20, 30, 20, 10, 20}); sort (v. begin(), v. end()); // 10 10 10 20 20 20 30 30 auto low = lower_bound (v. begin(), v. end(), 20); auto up = upper_bound (v. begin(), v. end(), 20); cout << "lower_bound at position " << (low - v. begin()) << 'n'; cout << "upper_bound at position " << (up - v. begin()) << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 29
Алгоритмы для упорядоченных входных интервалов // merge algorithm example #include <iostream> #include <algorithm> // merge, sort #include <vector> using namespace std; int main () { int first[] = {5, 10, 15, 20, 25}; int second[] = {50, 40, 30, 20, 10}; vector<int> v(10); sort (first, first+5); sort (second, second+5); merge (first, first+5, second+5, v. begin()); cout << "The resulting vector contains: "; for (auto x : v) cout << ' ' << x; cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 30
Алгоритмы для упорядоченных входных интервалов // inplace_merge example #include <iostream> #include <algorithm> // merge, sort #include <vector> using namespace std; int main () { int first[] = {5, 10, 15, 20, 25}; int second[] = {50, 40, 30, 20, 10}; vector<int> v(10); sort (first, first+5); sort (second, second+5); auto it = copy (first, first+5, v. begin()); copy (second, second+5, it); cout << "The vector before inplace_merge: "; for (auto x : v) cout << ' ' << x; cout << 'n'; inplace_merge (v. begin(), v. begin()+5, v. end()); cout << "The resulting vector contains: "; for (auto x : v) cout << ' ' << x; cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 31
Численные алгоритмы Эти алгоритмы выполняют разнообразную обработку числовых элементов и отличаются очень большой гибкостью и мощью. Например, алгоритм accumulate() по умолчанию вычисляет сумму элементов. Однако, если элементами контейнера являются строки, то вычисляется их конкатенация. А если переключиться с оператора + на оператор *, то алгоритм вычислит произведение элементов. • accumulate() – объединяет все значения элементов (выичсляет сумму, произведение и т. д. ) • inner_product() – объединяет все элементы двух интервалов • adjanced_difference() – для каждого элемента контейнера выполняется некоторая операция (по умолчанию – вычитание) с его предшественником, а результат сохраняется в другом контейнере • partial_sum() – объединяет каждый элемент со своими предшественниками Лекция 17 -21. 04. 2014 г. 32
Численные алгоритмы // accumulate example #include <iostream> #include <functional> // minus #include <numeric> // accumulate using namespace std; int myfunction (int x, int y) {return x+2*y; } struct myclass { int operator()(int x, int y) {return x+3*y; } } myobject; int main () { int init = 100; int numbers[] = {10, 20, 30}; cout << "using default accumulate: "; cout << accumulate(numbers, numbers+3, init); cout << 'n'; cout << "using functional's minus: "; cout << accumulate (numbers, numbers+3, init, minus<int>()); cout << 'n'; cout << "using custom function: "; cout << accumulate (numbers, numbers+3, init, myfunction); cout << 'n'; cout << "using custom object: "; cout << accumulate (numbers, numbers+3, init, myobject); cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 33
Численные алгоритмы // adjacent_difference example #include <iostream> #include <functional> // multiplies #include <numeric> // adjacent_difference using namespace std; int myop (int x, int y) {return x+y; } int main () { int val[] = {1, 2, 3, 5, 9, 11, 12}; int result[7]; adjacent_difference (val, val+7, result); cout << "using default adjacent_difference: "; for (auto x : result) cout << x << ' '; cout << 'n'; adjacent_difference (val, val+7, result, multiplies<int>()); cout << "using functional operation multiplies: "; for (auto x : result) cout << x << ' '; cout << 'n'; adjacent_difference (val, val+7, result, myop); cout << "using custom function: "; for (auto x : result) cout << x << ' '; cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 34
Численные алгоритмы // inner_product example. . . using namespace std; int myaccumulator (int x, int y) {return x-y; } int myproduct (int x, int y) {return x+y; } int main () { int init = 100; int series 1[] = {10, 20, 30}; int series 2[] = {1, 2, 3}; cout << "using default inner_product: "; cout << inner_product(series 1, series 1+3, series 2, init); cout << 'n'; cout << "using functional operations: "; cout << inner_product(series 1, series 1+3, series 2, init, minus<int>(), divides<int>()); cout << 'n'; cout << "using custom functions: "; cout << inner_product(series 1, series 1+3, series 2, init, myaccumulator, myproduct); cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 35
Численные алгоритмы // partial_sum example #include <iostream> #include <functional> // multiplies #include <numeric> // partial_sum using namespace std; int myop (int x, int y) {return x+y+1; } int main () { int val[] = {1, 2, 3, 4, 5}; int result[5]; partial_sum (val, val+5, result); cout << "using default partial_sum: "; for (auto x : result) cout << x << ' '; cout << 'n'; partial_sum (val, val+5, result, multiplies<int>()); cout << "using functional operation multiplies: "; for (auto x : result) cout << x << ' '; cout << 'n'; partial_sum (val, val+5, result, myop); cout << "using custom function: "; for (auto x : result) cout << x << ' '; cout << 'n'; return 0; } Лекция 17 -21. 04. 2014 г. 36
Лекция-2.10.ppt