STL源码剖析(三)

六、算法

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)改变容器接口者,称为容器适配器(例如 queuestack 都是配接器,修饰 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。这种配接器使我们能够将成员函数当作仿函数使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值