标准容器只是定义了一些基本的操作,也许用户还需要对顺序容器进行排序, 或者查找某个特定的元素, 或者查找最大或最小的元素。标准库对于这些潜在的操作并没有定义相关的成员函数,而是定义了相关的算法,这一组算法称为 泛型算法。
因为它们实现共同的操作,所以称之为 算法。 而泛型指的是 它们可以操作在多种容器类型上。 甚至某些内置数组或相关序列 以及 自定义的容器类型(只要与标准库兼容)同样可以使用这些泛型算法。
大多数算法一般是基于两个迭代器实现的。
概述:
举一个查找某一元素的例子:
1 int 型的 vector 对象, 名为vec
int search_value = 42;
vector<int>:: const_iterator result = find(vec.begin(), vec.end(), search_value);
2 list对象, lst
list<int>::const_iterator result - find(ist.begin(), ist.end(), search_value);
由于指针的行为与作用在内置数组中的迭代器一样,因此也可以使用find来搜索数组:
int ia[6]={27, ..., };
int search_value = 83;
int *result= find(ia, ia+6, search_value);
标准算法固有地独立于类型
这种算法,与容器的类型无关。但可能依赖元素类型,必须能够对元素做比较运算。其明确要求如下:
1 需要某种遍历集合的方式:能够从一个元素向前移动到下一个元素。
2 必须能够知道是否到达了集合的末尾。
3 必须能够对容器中的每一个元素与被查找的元素进行比较。
4
需要一个类型来指出元素在容器中的位置,或者表示找不到该元素。
迭代器将算法和容器绑定起来:
泛型算法用迭代器来解决第一个要求: 遍历容器。 所有的迭代器都支持自增操作符。并提供解引用操作访问元素的值。
迭代器用标记被查找的范围,第二个迭代器通常被称为 超出末端迭代器, 以此迭代器为返回值, 即可表示没有找到查找的元素。
算法从不基于容器操作 而是 基于迭代器操作,这是因为 基于迭代器的操作一般不会修改容器的内容。
标准库提供了100中算法。 与容器一样,算法有着一致的结构, 了解算法的设计能够使我们举一反三。
什么是算法:
使用泛型算法,必须包含
algorithm 头文件:
#include <algorithm>
标准库还定义了一组 泛化的 算术算法,其命名习惯与泛型算法相同。 使用这些算法则必须包含numeric头文件:
#include <numeric>
理解算法的最基本方法是了解该算法是否读元素、写元素或者对元素进行重新排序。
只读算法:
这种算法, 只会读取输入范围内的元素,而不会写这些元素。 find就是一个这样的算法。
另一个简单的只读算法是 accumulate。 该算法在numeric头文件中定义:
它的形式为: int sum = accumulate(vec.begin(), vec.end(), 0);
它表示对迭代器范围内的所有元素都进行累加并返回结果。
第三个参数非常有必要,它用于设定起始值,且与迭代器范围内的元素类型匹配。
find_first_of的使用:
该函数使用两队迭代器参数来标记 两端元素范围, 在第一段范围内查找 与第二段范围中任意元素匹配的元素, 然后返回一个迭代器,指向第一个匹配的元素。如果找不到匹配的元素,则返回第一个范围的end迭代器
size_t cnt = 0;
list<string>::iterator it = roster1.begin();
while ((it = find_first_of(it, roster1.end(), roster2.begin(), roster2.end())) !=
roster1.end()) {
++ cnt;
++ it;
}
一对迭代器标记的必须是一个范围,且第一个迭代器必须不断的自增 以达到第二个迭代器的位置
当出现find_first_of这样的函数时,
每对迭代器中,两个实参的类型必须精确匹配,但不要求两队之间的类型匹配。
写容器元素的算法:
在使用一些算法写元素时 ,必须确保算法所写的序列至少足以存储要写入的元素。
1
写入输入序列的元素:
写入到输入序列的算法本质上是安全的 只会写入与制定输入范围数量相同的元素。
fill(vec.begin(), vec.end(), 0);
vec为输入序列, 第三个参数为写入的值。
这种算法的好处是,它能够安全的写入到输入序列中。
2
不检查写入操作的算法:
fill_n 函数带有的参数包括: 一个迭代器,一个计数器 和 一个值。 该函数从迭代器指向的元素开始,将制定数量的元素设置为给定的值。
3
引入back_inserter (与fill_n的结合 将导致安全的操作)
确保算法有足够的元素 存储输出数据的一种方法是使用 插入迭代器。 插入迭代器是可以给 基础容器添加元素的迭代器。
使用back_inserter 的程序必须包含 iterator头文件
back_inserter是一个迭代器适配器,它使用一个对象作为实参,并生产一个适应期实参行为的新对象。
fill_n (back_inserter(vec), 10, 0);
这样,fill_n函数每写入一个值,都会通过back_inserter生成的插入迭代器实现。
fill_n是向输出数据序列 写入值, 其目标迭代器是 指向目标序列的第一个元素。
4
写入目标迭代器的算法
第三类算法向目标迭代器写入未知个数的元素, 正如fill_n函数一样,其目标迭代器也是指向目标序列的第一个元素。
copy函数就是一个这样的简单算法:
vector<int> ivec; // empty vector
copy (ilist.begin(), ilist.end(), back_inserter(ivec));
5
算法的_copy版本
带有copy的算法,不会修改原序列的值,二回创建一个新的序列。
如replace算法,它就会修改序列。但是replace_copy会创建一个新的序列
replace(list.begin(), list.end(), 0, 42);
第三个参数 是list序列中的值, 第四个参数是要替换的值。
而replace_copy算法 需要接受5个参数:
replace(list.begin(), list.end(), back_inserter(ivec), 0, 42);
对容器元素重新排序的算法:
例: 比如要统计 一本书里 出现的单词个数,并以单词的长度来输出,且同长度的单词以字典顺序输出。
则需要满足一下功能:
1 需要将书本中的所有单词都存放在一个 vector的容器中
2 对该容器 去除其重复的单词
3 统计字母不小于六的单词
4 对容器中的单词以字典顺序排序
2
去除重复:
首先,应该用sort算法,对字典中的单词进行排序, 其格式为:
sort(vec.begin(), vec.end());
unique算法:
它带有两个指定元素范围的迭代器参数。 该算法删除相邻的重复元素,然后重新排列输入范围内的元素,并且返回一个迭代器end_unique,表示无重复的值范围的结束。
即 将这个迭代器当做无重复范围的最后一个元素的下一个元素,也就是删除的重复的元素的第一个元素。 而删除的元素都是放在vector序列的最后面的。
以上算法并没有正在删除元素。如果想删除元素,还必须使用容器操作删除元素:
使用容器操作删除元素:
调用erase实现该功能。 从end_unique指向的元素开始删除。
3
统计字母个数不小于六的单词:
为了解决该问题,需要调用stable_sort, count_if算法, 这两个算法都需要谓词函数作为第三个参数。
谓词函数一般都是用is开头并返回bool型,提供了比较的约束的条件
stable_sort的谓词函数可以定义为:
bool isShorter( const string &s1, const string &s2)
{
return s1.size() <s2.size()
}
那么count_if的 谓词函数可以定义如下:
bool isGT(const string &s1)
{
return s1.size()>=6;
}
4
最后按长度顺序输出
可以用map 即 将word作为第一个参数, 将长度作为第二个参数
然后按 第二个参数 进行sort排序 然后输出
5 介绍:
make_plural
这个是根据单词个数,觉得输出不同的字符串,即 是 单数还是复数:
string make_plural(int size_t, string &s1, string &s2)
{
return (if(size<=1))? s1: s1+s2;
}