STL算法、迭代器和映射总结
迭代器
迭代器的提出
算法函数独立于数据结构无疑是一种很好的思路,它高度体现了OOP的核心思想。
但很快,我们就会发现因各数据结构的访问形式不同,困难显而易见。比如:
int sum( vector<int> &v ) // 对向量中的元素求和
{
int s = 0;
for ( int i = 0; i < v.size(); ++i ) s += v[i];
return s;
}
int sum( List<int>& l ) // 对链表中的元素求和
{
int s = 0;
Link<int> *first = l.first;
while ( first != l.last ) {
s += first ->elem;
first = first ->next;
}
return s;
}
从而我们设想必须有一种通用的访问工具。这就是迭代器。将算法和容器隔离开的过程叫做解耦。
迭代器的分类
类型 | 支持的操作 | 容器举例 |
---|---|---|
前向迭代器 | 向前移动;读取;相等比较 | 单向list |
双向迭代器 | 前向操作;向后移动 | 双向list |
随机访问迭代器 | 双向操作;自由移动;任意读取;大小比较;相减 | vector |
使用模型
在定义数据序列的功能上说,迭代器是一种可以表示数据序列中元素的对象。
数据序列:
begin()
开始,end()
结束- 每个元素可以通过
*
进行解引用,取出对应位置的元素。 - 迭代器可以支持基本运算,实现在数据序列中的遍历。
常用算法
查找函数
find(begin(),end())
函数,遍历查找。
find_if(begin(), end(), pred())
,为查找添加条件。
谓词predicates
其中 pred()
就称为谓词。
谓词的形式:
- 函数
- 函数对象
bool odd(int i) return i%2;//函数形式的谓词
struct Odd
{
bool operator(int i) const { return i%2;}
} odd;//重载括号的函数对象
if (odd(n))//两者均可用
{ ... }
vector<int>::iterator p = find_if(v.begin(), v.end, odd);
vector<int>::iterator p = find_if(v.begin(), v.end, Odd()); //创建临时函数对象
从上面的例子可以看出,在调用过程当中,二者实际上并无太大差异。
那为什么要舍近求远用函数对象呢?
思考: C++的类,相比函数,优势在于结合了结构体变量携带状态的特点。从而可以在运行时改变函数的非入口特征值。这是仅有一个入口的普通函数谓词所不能比拟的。
例如以下这个门限值的判定
template<class T>
struct Less_than
{
T val; // 判定门限值
Less_than(T& x) : val(x) { }
bool operator()(const T& x) const { return x < val; }
};
// 在 vector<int> 查找值小于 43 的元素
p = find_if(v.begin(), v.end(), Less_than(43));
//在 list<string> 查找字典序在单词 “perfection” 之前的元素
q = find_if(ls.begin(), ls.end(), Less_than("perfection"));
函数对象的优点再论
- 可以抽象成一类函数的判别,并可以在运行过程中改动。
- 易于内联:现有编译器支持性好
- STL中已有一类成熟的函数对象模板
plus<T>
minus<T>
multiplies<T>
divides<T>
modulus<T>
multiplies<double> m;
m( x, y );
累加算法
定义在可泛化为累乘、累除、累减。
// 泛化为累乘算法
#include <numeric>//这个算法定义在numeric中
#include <functional>
void f(list<double>& ld)
{
double product = accumulate(ld.begin(), ld.end(), 1.0, multiplies<double>());
//...
}
内积算法
向量内积的计算方式,给出两个数据序列,第一个给出头尾,第二个给出头。结果为一个数。
// 计算加权平均成绩
#include <vector>
#include <numeric> >
int main()
{
vector<double> score { 92, 77, 85, …. }; // 每门课程的成绩
vector<double> weight { 4, 2, 3, … }; // 每门成绩的权重
double average = inner_product(score.begin(), score.end(),weight.begin(), 0.0) /
accumulate(weight.begin(), weight.end());
return;
}
内积算法也可以进行泛化。
其他常用标准库算法
算法 | 用法 |
---|---|
for_each(b,e,op) | 对区间 [b,e) 的每一个元素执行 op |
r=find(b,e,v) | 在区间 [b,e) 查找给定值 v 的元素 |
r=find_if(b,e,p) | 在区间 [b,e) 查找符合条件 p(x) 的元素 |
x=count(b,e,v) | 在区间 [b,e) 计算值为给定值 v 的元素的个数 |
x=count_if(b,e,p) | 在区间 [b,e) 计算符合条件 p(x) 的元素的个数 |
r=remove(b,e,v) | 删除区间 [b,e) 中值为给定值 v 的元素 |
r=remove_if(b,e,p) | 删除区间 [b,e) 中符合条件 p(x) 的元素 |
fill(b,e,v) | 用给定值 v 填充区间 [b,e) 的元素 |
sort(b,e) | 对区间 [b,e) 的元素进行排序,排序条件: < |
sort(b,e,p) | 对区间 [b,e) 的元素进行排序,排序条件: p |
copy(b,e,b2) | 复制区间 [b,e)中的元素至另一区间(b2 起始)*dest++ = *src++; |
unique_copy(b,e,b2) | 复制区间 [b,e)中的元素至另一区间,剔除相邻相同元素 |
merge(b,e,b2,e2,r) | 合并两个区间 [b,e), [b2,e2) 的元素至第三个区间(r 起始) |
equal(b,e,b2) | 区间 [b,e) 与区间 [b2,b2+(e-b)) 的所有元素是否相等? |
算法小结
- 面向一个或更多的序列进行处理
- 通常通过一对迭代器定义序列的范围
- 支持一个或多个类型的操作
- 利用函数对象定义操作类型
- 利用普通函数定义操作类型
- 一般通过返回指向序列尾部的迭代器报告“错误或操作失败”
关联容器与映射
map
是仅次于vector
的STL容器。- 基于平衡二叉树实现
- 支持通过下标访问元素
映射的常见操作
标准操作:begin(), end(), size(), empty(), clear()
插入操作:
age.insert( pair<string,int>{ “Andy”, 10 } ); // 插入1 个键值对(“Andy”, 102)
age.insert( make_pair(“Andy”, 10 ) ) ; // 利用 make_pair() 函数插入
下标操作:
int n = age.[“Jack”]; // 若键值 “Jack” 存在,返回年龄值
//否则,将键值对 (“Jack”, int{}) 插入 age,返回 int{}
age[“Andy”] = 12; // 若键值 “Andy” 存在,改变年龄值为 12
// 否则将键值对 ( “Andy”, int{12} ) 插入age,返回 int{12}
删除操作:
age.erase(“Andy”); // 删除键值为 “Andy” 的元素
查找操作:map
的查找非常高效
map<string,int>::iterator it = age.find(“Andy”); // 查找指定键值的元素
if( it != age.end() ) cout<<it->first<<it->second<<endl; //找到,打印输出
集合set
去除了映射中的值维,无下标操作。键不重复,同样利用平衡二叉树高度有序。
例 利用set实现字典
int main()
{
// 利用输入流初始化 set
set<string> words{ istream_iterator<string>{ cin },
istream_iterator<string>{ } } );
// 利用拷贝算法将 set 中的元素输出至输出流
copy( words.begin(), words.end(),
ostream_iterator<string>{cout, "\n"} );
}
字典如果使用vector实现,则需要先读入,后排序,再 unique_copy
。使用set
极大减少了因顺序和判断重复造成的开销。