概述
STL算法都被设计用来处理一个或者多个iterator区间,一般第一个区间提供起点和重点,第二个区间提供起点即可。
某些STL算法允许调用者传递自定义操作,这些操作可以是普通函数,函数对象,或者lamda表达式。比如,find_if算法允许第三个参数传递一个自定义操作,查找满足此操作(即结果返回true)的那个元素;有比如min_element允许传入第三个参数定义自己的比较规则。
分类
有些算法是只读的,有些会改变元素自身,还有则改动元素顺序,一般可将其分为以下几类:
- 不变序列算法
- 变值算法
- 删除算法
- 变序算法
- 排序算法
- 有序区间算法
- 数值算法
其中某些算法可能属于多个类,则将其放入最贴切的分类中。
1.不变序列算法
此类算法不会修改所作用的容器或对象,适于所有容器。它们的时间复杂度都是 O(n)的。
名称 | 效果 |
---|---|
min | 求两个对象中较小的 (可自定义比较器 可自定义比较器 ) |
max | 求两个对象中较大的 (可自定义比较器 可自定义比较器 ) |
min_element | 求区间中的最小值 (可自定义比较器 ) |
max_element | 求区间中的最大值 (可自定义比较器 ) |
for_each | 对区间每个元素做某种操作 |
count | 计算区间中等于某值的元素个数 |
count_if | 计算区间中符合某种条件的元素个数 计算区间中符合某种条件的元素个数 |
find | 在区间中查找等于某值的元素 |
find_if | 在区间中查找符合某条件的元素 |
find_end | 在区间中查找另一个区间最后一次出现的位置 (可自定义比较器 ) |
find_first_of | 在区间中查找第一个出现在另一个区间中的元素 (可自定义比较器) |
adjacent_find | 在区间中寻找第一次出现连续两个相等元素的位置 (可自定义比较器 ) |
search | 在区间中查找另一个区间第一次出现的位置 (可自定义比较器 ) |
search_n | 在区间中查找第一次出现等于某值的连续 n个元素(可自定义比较器 ) |
equal | 判断两区间是否相等 (可自定义比较器 ) |
mismatch | 逐个比较两区间的元素,返回第一次发生不相等的两个元素的位置 (可自定义比较器 ) |
lexicographical_compare | 按字典序比较两个区间的大小 (可自定义比较器 ) |
举几个例子:
count_if
template<typename InIt, typename Pred>
size_t count_if(InIt first,InIt last,Pred pr);
计算[first,last)中符合pr(e)==true的元素e的个数
//查找v中元素值等于4的元素
vector<int> v{1,2,3,4,5};
count_if(v.begin(),v.end(),[](int n)->bool{return n==4;})
顺便复习lamda表达式,lamda表达式是一个可调用对象,可将其理解为一个未命名的内联函数,可以定义在函数内部,具有如下形式:
[捕获列表] (参数列表) -> 返回类型 {函数体}
捕获列表指lamda使用其所在函数的参数列表(包括值捕获和引用捕获),lamda参数列表中不能有默认参数。
min_element
max_element
class A {
public: int n;
A(int i):n(i){}
};
bool operator<(const A & a1, const A & a2) {
if(a1.n==3 && a2.n==7)
return true;
return false;
};
int main() {
A a[5] = {3,5,7,2,1};
cout<<min_element(a,a+5)<<endl;
cout<<max_element(a,a+5)<<endl;
}
min_element默认是采用 "<"进行比较的,首先它会先把最小值min初始化*a,然后判断区间中的下一个元素 *next < min?,如果成立,就让min=*next,再判断下一元素,对于重载的 “<”,也就是执行operator<(*next,min);最后输出3。
max_element默认也是采用 "<"进行比较的,它执行的则是operator(max,*next),最后输出的是7。
2.变值算法
此类算法会修改源区间或目标区间的值,值被修改的那个区间,不可以是属于关联容器的(set,multiset,map,multimap,以及unordered版本)
名称 | 效果 |
---|---|
for_each | 对区间中每个元素做某种操作 |
copy | 复制一个区间到别处 |
copy_backward | 复制一个区间到别处,目标区间是从后往前被修改 |
transform | 将一个区间元素变形后拷贝到另一区间 |
swap_ranges | 交换两个区间的内容 |
fill | 用某个值填充区间 |
fill_n | 用某个值替换区间中的n个元素 |
generate | 用某个操作的结果填充区间 |
generate_n | 用某个操作的结果替换区间中的n个元素 |
replace | 将区间中的某个值替换为另一个值 |
replace_if | 将区间中符合某种条件的值替换成另一个值 |
replace_copy | 将一个区间拷贝到另一个区间,拷贝时某个值要换成新值拷贝过去 |
replace_copy_if | 将一个区间拷贝到另一个区间,拷贝时符合条件的某个值要换成新值拷贝过去 |
举例:
transform
template<typename InIt,typename OutIt,typename Unop>
OutIt transform(InIt first,InIt last,OutIt x,Unop uop);
对[first,last)中的每个迭代器I,执行uop(*I),并将结果放入x开始的地方,要求uop(*I)不改变 *I的值。
3.删除算法
删除算法会一个容器里的某些元素。这所说的“删除”,并不会使容器里的元素减少,其工作过程是:将所有应该被删除的元素看做空位子,然后用留下的元素从后往前移 ,依次去填空位子。元素往前移后,它原来的位置也就算是空子,也应由后面留下的元素来填上。最后,没有被填上的空位子,维持其原来的值不变。 删除算法不应该作用于关联容器。
名称 | 效果 |
---|---|
remove | 删除区间中等于某个元素的值 |
remove_if | 删除区间中满足某个条件的值 |
remove_copy | 拷贝区间到另一个区间,等于某个值的元素不拷贝 |
remove_copy_if | 拷贝区间到另一区间,符合某种条件的元素不拷贝 |
unique | 删除区间中连续相等的元素,只留下一个(可自定义比较器) |
unique_copy | 拷贝区间到另一个区间,连续相等的元素,只拷贝第一个到目标区间(可自定义比较器) |
举例:
unique
template<typename FwdIt,typename Pred>
FwdIt unique(FwdIt first,FwdIt last,Pred pr)
用pr比较是否相等。对于[first,last)区间中连续相等的元素,只留第一个。返回迭代器,指向元素删除后的区间的最后一个元素的后面。
4.变序算法
变序算法改变容器中元素的位置,但是不改变元素的值,变序算法不适用于关联容器。此类算法复杂度都是O(n)的。
名称 | 效果 |
---|---|
reverse | 颠倒区间的前后次序 |
reverse_copy | 把一个区间颠倒后的结果拷贝到另一个区间,源区间不变 |
rotate | 将区间进行循环左移 |
rotate_copy | 将区间以首尾相接的形式进行旋转后的结果拷贝到另一个区间,源区间不变 |
next_permutation | 将区间改为下一个排列(可自定义比较器) |
pre_permutation | 将区间改为上一个排列(可自定义比较器) |
random_shuffle | 随机打乱区间内元素的顺序,使用前要初始化伪随机种子 |
partition | 把区间内满足某个条件的元素移到前面,不满足该条件的移到后面 |
stable_partition | 把区间内满足某个条件的元素移到前面,不满足该条件的移到后面。并且对于这两部分元素,分别保持它们原来的先后次序不变 |
举例:
template<typename InIt>
bool next_premutation(InIt first,InIt last)
string str = "213";
while(next_premutation(str.begin(),str.end()))
{
cout<<str<<endl;
}
分析:next_permutation默认用operator < 比较元素,会改变[first,last)区间元素的次序,使它们符合“下一次排列次序”。如果元素排成正规次序(也就是字典顺序),则算法返回false。因此输出为:231\n312\n321\n
5.排序算法
排序算法比变序算法复杂度高,一般是O(nlogn)的,排序算法需要随机访问迭代器的支持,因此不适用于关联容器和list。
名称 | 效果 |
---|---|
sort | 将区间从小到大排序(可自定义比较器) |
stable_sort | 区间从小到大排序,并保持相等元素间的相对次序(可自定义比较器) |
partial_sort | 对区间部分排序,直到最小的n个元素就位(可自定义比较器) |
partial_sort_copy | 将区间前n个元素的排序结果拷贝到别处,源区间不变(可自定义比较器) |
nth_element | 对区间部分排序,使得第n小的元素(n从0开始算)就位,而且比它小的都在它前面,比它大的都在它后面(可自定义比较器) |
make_heap | 使区间称为一个“堆”(可自定义比较器) |
push_heap | 将元素加入一个是“堆”的区间(可自定义比较器) |
pop_heap | 从“堆”区间中删除堆顶元素(可自定义比较器) |
sort_heap | 将一个“堆”区间排序,排序结束后,该区间就是普通的有序区间,不再是“堆”了(可自定义比较器) |
sort实际上是快速排序,时间复杂度O(nlogn);平均性能最优,但是最坏情况下,性能非常差。
stable_sort实际上是归并排序,特点是能保持相等元素之间的先后次序;有足够内存的情况下,复杂度为nlogn,否则复杂度为nlognlogn。
排序算法要求催寄存区迭代器支持,因此不适用于关联容器和list。
堆排序:堆是一种二叉树,最大元素总是在堆顶上,二叉树任何节点的子节点总是小于等于父节点的值。即k(i)>=k(2i+1)且k(i)>=k(2i+2),其中i=0,1,2…。对的各种排序算法,需要随机访问迭代器支持。
举例:
//按升序排序。判断x是否应该比y靠前,就看x < y是否为true
template <typename RanIt>
void sort(RanIt first,RanIt last);
//按升序排序。判断x是否应该比y靠前,就看pr(x,y)是否为true
template <typename RanIt,typename Pred>
void sort(RanIt first,RanIt last,Pred pr);
class MyLess{
public:
//n2个位数大于n1个位数,就返回true
bool operator()(int n1,int n2){
return n1%10 < n2%10;
}
};
int main(){
int a[] = {14,2,9,111,78};
sort(a,a+5,MyLess());
for(auto &ai:a){
cout<<ai<<" ";
}
cout<<endl;
sort(a,a+5,greater<int>());
for(auto &ai:a){
cout<<ai<<" ";
}
}
分析:MyLess类重载了operator()运算符,因此MyLess()是一个函数对象,可以直接作为sort的第三个参数,greater< int> () 同理;第一个sort判断pr(x,y)是否为true的逻辑是x的个位数小于y的个位数,就返回true,如果pr(x,y)是true,x就应该排在y的前面(因为是升序排列),也就是说谁的个位数小,谁就排在前面。第二个sort判断pr(x,y)是否是true的逻辑是x大于y,就返回true,也就是说大的应该排在前面。因此最终输出为:111 2 14 78 9 \n 111 78 14 9 2\n.
6.有序区间算法
有序区间算法要求所操作的区间是已经从大到小排好序的,而且需要随机访问迭代器的支持。所以有序区间算法不能用于关联容器和list。
名称 | 效果 |
---|---|
binary_search | 判断区间中是否包含某个元素 |
includes | 判断是否一个区间中的每个元素都在另一个区间 |
lower_bound | 查找最后一个不小于某值的元素到的位置 |
upper_bound | 查找第一个大于某值的元素的位置 |
equal_range | 同时获取lower_bound和upper_bound |
merge | 合并两个有序区间到第三个区间 |
set_union | 将两个有序区间的并拷贝到第三个区间 |
set_intersection | 将两个有序区间的交拷贝到第三个区间 |
set_difference | 将两个有序区间的差拷贝到第三个区间 |
set_symmetric_difference | 将两个有序区间的对称差拷贝到第三个区间 |
inplace_merge | 将两个连续的有序区间原地合并为一个有序区间 |