六、算法
STL算法的一般形式:所有泛型算法的前两个参数都是一对迭代器,first、last,用以标示算法的操作区间。STL习惯采用前闭后开区间表示法,写成 [ first, last ), 当 first==last ,表示 空区间
accumulate 累记算法
必须提供一个初值 init ,确保当 [ first, last) 为空时,仍能返回值。
copy 函数
SGI STL的copy算法用尽各种办法,包括函数重载、类型特性、偏特化等编程技巧来尽可能地加强效率
copy 将输入区间 [first,last)
内的元素复制到输出区间 [result,result+(last-first))
内,也就是说,它会执行赋值操作*result = *first,*(result+1) = *(first+1),...
依次类推。返回一个迭代器:result+(last-first)
。copy 对其template参数所要求的条件非常宽松。其输入区间只需由 inputIterators 构成即可,输出区间只需要由 OutputIterator 构成即可。这意味着可以使用copy算法,将任何容器的任何一段区间的内容,复制到任何容器的任何一段区间上
set相关算法
这部分介绍的4个算法所接受的set,必须是有序区间,元素可能重复。换句话说,它们可以接受STL的 set/multiset 容器作为输入区间。hash_set/hash_multiset 两种容器,以hashtable为底层机制,其内的元素并未呈现排序状态,所以虽然名称中也有set字样,却不可应用于这里的4个算法。
set_union:求集合s1和s2的并集。
set_intersection:求集合s1和s2的交集。
set_difference:该函数计算两个集合的差集。
其他算法
count() 运用 equality 操作符,将 [ first,last ) 区间内的每一个元素拿来和指定的 value 比较,并返回相等的元素个数。
merge() 应用于有序区间,将两个经过排序的集合S1和S2,合并放置在另一段空间
remove() 移除(但不删除)。例如序列{0,1,0,2,0,3,0,4},如果我们执行remove(),移除所有0值元素,执行结果是{1,2,3,4,0,3,0,4},将每一个和 value 不相等的元素轮番赋值给 first 之后的空间。如例子所示,第四个位置之后的是算法的残余数据。区间大小并不发生变化,需要移除的元素会被后面的覆盖,区间尾部会有残余,返回指向第一个残余元素的迭代器。
replace() [ first,last )区间内的所有old_value 都被 new_value 取代
reverse() 颠倒排序
unique( ) 移除重复的元素,但只能移除相邻的重复元素,如果要移除所有(包括不相邻的)重复元素,必须先排序。
lower_bound( ) 二分查找的一种版本,试图在已排序的 [ first,last )中寻找元素value,若不存在,返回第一个“不小于value”的元素位置
upper_bound( ) 二分查找的一个版本,试图寻找在 “ 不破坏顺序的情况下,可插入value的最后一个合适的位置 ”
binary_search( ) 应用于有序区间。binary_search 利用lower_bound 先找出“假设value存在的话,应该出现的位置”,然后再对比该位置上的值是否为我们所要查找的目标,返回对比结果。
for_each( ) 将仿函数f 施行于 [ first,last )区间内的每一个元素身上。
template <class InputIterator,class Function>
Function for_each (InputIterator first,InputIterator last,Function f){
for( ; first != last; first++ )
f( *first );
return f;
}
sort( )
接受两个 RandomAccessIterators (随机存取迭代器) ,将区间内所有元素递增排列。STL的所有关联式容器(set,map 等)都拥有自动排序功能,不用sort 算法,序列式容器中的stack、queue和prioprity-queue 都有特别的出入口,不允许用户对元素排序, vector 、deque 和 list , 前两者迭代器属于 RandomAccessIterators ,适合用sort 算法 ,而list是BidirectionalIterator,不适合。
STL的sort算法,数据量大,采用Quick Sort,分段递归排序。一旦分段后的数据量小于某个门槛,为避免Quick Sort的递归调用带来过大的额外负荷,就改用Insertion Sort。如果递归层次过深,还会改用Heap Sort。 Introsort,混合式排序算法。其行为在大部分情况下几乎与Quick sort 相同,但当分割行为有恶化为二次行为的倾向时,改用Heap Sort, 使效率维持在O(N log N),又比一开始就使用heap sort 效果好。
sort 源码
// sort()只适用于 RandomAccessIterator
template <class RandomAccessIterator, class Compare>
inline void sort(RandomAccessIterator first, RandomAccessIterator last,
Compare comp) {
if (first != last) {
__introsort_loop(first, last, value_type(first), __lg(last - first) * 2,
comp);
__final_insertion_sort(first, last, comp);
}
}
其中的_lg()用来控制分割恶化的情况。
// 找出 2^k <=n 的最大值 k,例:n=7,k=2,n=20,k=4;
template<class Size>
inline Size _lg(Size n){
Size k;
for(k=0;n>1;n>>=1) ++k;
return k;
}
_introsoft_loop()算法如下:
template <class RandomAccessIterator, class T, class Size, class Compare>
void __introsort_loop(RandomAccessIterator first,
RandomAccessIterator last, T*,
Size depth_limit, Compare comp) {
while (last - first > __stl_threshold) { //设置一个阈值
if (depth_limit == 0) { //分割恶化
partial_sort(first, last, last, comp); //改用 heapsort
return;
}
--depth_limit;
// 选择一个够好的枢轴并决定分割点(与quickt sort 类似)
RandomAccessIterator cut = __unguarded_partition
(first, last, T(__median(*first, *(first + (last - first)/2),
*(last - 1), comp)), comp);
__introsort_loop(cut, last, value_type(first), depth_limit, comp); //递归
last = cut;
}
}
当 _introsort_loop() 结束,[ first,last) 内有多个“元素个数小于16”的子序列,每个子序列都有相当程度的排序,但尚未排序,回到母函数 sort(), 再进入 _final_insertion_sort()
merge sort() 归并排序,时间复杂度O(N logN),需借用额外内存空间,效率比不上quick sort。
七、仿函数
仿函数是一种将 operator( )重载的class template。在STL标准规格定案后,仿函数采用函数对象(function object)作为新名。主要用途是:搭配 STL 算法。
如 accumulate ( iv.begin(), iv.end(), multiplies<int>() );
就实现而言,仿函数其实就是一个“行为类似函数”的对象,为了能够“行为类似函数”,其类别定义中必须自定义function call运算子。拥有这样的运算子后,就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator()。
任何应用程序欲使用STL內建的仿函数,都必须含入<functional>头文件,SGI则将它们实际定义于<stl_function.h>头文件
为了拥有适配能力,每一个仿函数必须定义自己的相应类型。就像迭代器如果要融入整个STL大家庭,也必须依照规定定义自己的5个相应类型一样。这些相应类型是为了让适配器能够取出,获得仿函数的某些信息
仿函数的相应类型主要用来表现函数参数类型和传回值类型
为方便起见,<stl_function.h>定义了两个classes,分别代表一元仿函数和二元仿函数(STL不支持三元仿函数),其中没有任何data members或member functions,唯有一些类型定义。任何仿函数只要依据需求选择继承其中一个class,就自动拥有了那些相应类型,也就拥有了适配能力。
unary_function 用来呈现一元函数的参数类型和返回值类型:
template <class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};
binary_function 用来呈现二元函数的第一参数类型,第二参数类型,以及返回值类型:
template <class Arg1, class Arg2, class Result>
struct binary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
八、配接器
adapter:将一个 class 的接口转换为另一个class 接口,使原本因接口不兼容而不能合作的classes,可以一起运作。
STL所提供的各种适配器中:1)改变仿函数接口者,称为函数适配器;2)改变容器接口者,称为容器适配器(例如 queue、stack 都是配接器,修饰 deque 接口);3)改变迭代器接口者,称为迭代器适配器。
函数适配器(function adapter)
注意,所有期望获得适配能力的组件,本身都必须是可适配的。换句话说,1)一元仿函数必须继承自unary_function;2)二元仿函数必须继承自binary_function;3)成员函数必须以mem_fun处理过;4)一般函数必须以ptr_fun处理过。一个未经ptr_fun处理过的一般函数,虽然也能以函数指针的形式传给STL算法使用,却无法拥有任何适配能力。
函数配接器,配接操作包括 bind、negate、compose以及对一般函数或成员函数的修饰(使其成为一个仿函数)
bind2st 绑定第二参数的function adapter 如:bind2nd(less<int>(),12)
//以下适配器用来表示某个 "可适配 binary function" 转换为 “unary function”
template <class Operation>
class binder2nd
: public unary_function<typename Operation::first_argument_type,
typename Operation::result_type> {
protected:
Operation op; //内部成员
typename Operation::second_argument_type value; //内部成员
public:
binder2nd(const Operation& x,
const typename Operation::second_argument_type& y)
: op(x), value(y) {} //将表达式和第二参数记录于内部成员
typename Operation::result_type
operator()(const typename Operation::first_argument_type& x) const {
return op(x, value); //实际调用表达式,并将value绑定为第二参数
}
};
//辅助函数,使我们得以更方便使用binder2nd
template <class Operation, class T>
inline binder2nd<Operation> bind2nd(const Operation& op, const T& x) {
//先把x转型为op的第一参数类型
typedef typename Operation::second_argument_type arg2_type;
return binder2nd<Operation>(op, arg2_type(x));
}
not1(bind2nd(less<int>(),12)) 再适配修饰一次
//以下适配器用来表示某个 "可适配 predicate" 的逻辑负值
template <class Predicate>
class unary_negate
: public unary_function<typename Predicate::argument_type, bool> {
protected:
Predicate pred; //内部成员
public:
explicit unary_negate(const Predicate& x) : pred(x) {}
bool operator()(const typename Predicate::argument_type& x) const {
return !pred(x); //将pred的运算结果加上否定运算
}
};
//辅助函数,使我们得以更方便使用unary_negate
template <class Predicate>
inline unary_negate<Predicate> not1(const Predicate& pred) {
return unary_negate<Predicate>(pred);
}
使用函数指针:ptr_fun。这种配接器使我们能够将一般函数当作仿函数使用。
//以下适配器其实就是把一个一元函数指针包起来
//当仿函数被动调用时,就调用该函数指针
template <class Arg, class Result>
class pointer_to_unary_function : public unary_function<Arg, Result> {
protected:
Result (*ptr)(Arg); //内部成员,一个函数指针
public:
pointer_to_unary_function() {}
//构造函数,将函数指针记录于内部成员中
explicit pointer_to_unary_function(Result (*x)(Arg)) : ptr(x) {}
//通过函数指针指向函数
Result operator()(Arg x) const { return ptr(x); }
};
//辅助函数,让我们得以方便使用pointer_to_unary_function
template <class Arg, class Result>
inline pointer_to_unary_function<Arg, Result> ptr_fun(Result (*x)(Arg)) {
return pointer_to_unary_function<Arg, Result>(x);
}
用于成员函数指针:men_fun。这种配接器使我们能够将成员函数当作仿函数使用。