STL算法_sort算法篇

STL算法_sort算法篇

  • 简介

    sort算法接受两个RandomAccessIterators(随机存取迭代器),然后将区间内的所有元素以渐增方式由小到大重新排序。STL中的关系型容器都拥有自动排序功能(底层使用RB-tree实现),不需要用到sort算法。至于序列式容器中的stack、queue和priority-queue都有特别的出入口,不允许用户对元素排序。剩下vector、deque和list,前者的迭代器属于RandomAccessIterators,适合使用sort算法,list迭代器属于BidirectionalIterators,不在STL标准之列的slist,其迭代器更属于ForwardIterators,都不适用于sort算法。如果要对list或slist排序,应该使用它们自己提供的member functions sort()。

    排序在日常生活中非常重要。字典需要排序,书籍索引需要排序,磁盘目录需要排序,名片需要排序,图书馆藏需要排序,户籍数据需要排序。任何数据只要想快速查找,就需要排序。排序可以改善效率。

    STL中的sort算法,数据量大时采用Quick Sort,分段递归排序,一旦分段后的数据量小于某个门槛,为避免Quick Sort的递归调用带来过大的额外负担,就改用Insertion Sort。如果递归层次过深,改用Heap Sort。STL中排序是使用Quick Sort和Insertion Sort完成的。

  • 1)插入排序算法

    Insertion Sort算法

    以双层循环形式进行。外循环遍历整个序列,每次迭代决定出一个子区间;内循环遍历子区间内的每一个“逆转对”倒转过来。逆转对指任何两个迭代器i,j,i < j而*i > *j。一旦不存在逆转对,则排序完毕。时间复杂度为O(N^2)。数据量较少时效果比较好。STL中提供了两个版本实现。版本一:以渐增方式排序,即以operator<为两个元素的比较函数;版本二:允许用户指定一个仿函数,作为两元素的比较函数。

    // 版本一辅助函数
    // 此函数命名为unguarded_x原因:一般的insertion Sort在内循环原本需要做两次判断,判断是否相邻两个元素是
    // 逆转对,同时也判断循环是否超越边界。由于源码会导致最小值必然在内循环子区间的最边缘,所以两个判断合并
    // 为一个判断,故称为unguarded_。
    template 
    void __unguarded_linear_insert(_RandomAccessIter __last, _Tp __val) {
      _RandomAccessIter __next = __last;
      --__next;
      // insertion sort的内层循环
      // 注意:一旦不在出现逆转对,循环就可以结束
      while (__val < *__next) {   // 逆转对存在
        *__last = *__next;        // 调整
        __last = __next;          // 调整迭代器
        --__next;                 // 左移一个位置
      }
      *__last = __val;            // value的正确落脚处
    }
    // 版本2
    template 
    void __unguarded_linear_insert(_RandomAccessIter __last, _Tp __val, 
                                   _Compare __comp) {
      _RandomAccessIter __next = __last;
      --__next;  
      while (__comp(__val, *__next)) {
        *__last = *__next;
        __last = __next;
        --__next;
      }
      *__last = __val;
    }
    // 版本一辅助函数
    template 
    inline void __linear_insert(_RandomAccessIter __first, 
                                _RandomAccessIter __last, _Tp*) {
      _Tp __val = *__last;                           // 记录尾元素
      if (__val < *__first) {                        // 尾比头还小(注意,头端必须为最小元素)
        // 那么就不用一个一个比较了,一次做完即可。
        copy_backward(__first, __last, __last + 1);  // 将整个区间向右递移一个位置
        *__first = __val;                            // 令头元素等于原先的尾元素值
      }
      else                                           // 尾不小于头
        __unguarded_linear_insert(__last, __val);
    }
    // 版本2
    template 
    inline void __linear_insert(_RandomAccessIter __first, 
                                _RandomAccessIter __last, _Tp*, _Compare __comp) {
      _Tp __val = *__last;
      if (__comp(__val, *__first)) {
        copy_backward(__first, __last, __last + 1);
        *__first = __val;
      }
      else
        __unguarded_linear_insert(__last, __val, __comp);
    }
    // Insertion Sort算法:Insertion Sort以双层循环形式进行。外循环遍历整个序列,每次迭代决定出一个子区间;
    // 内循环遍历子区间内的每一个“逆转对”倒转过来。逆转对指任何两个迭代器i,j,i*j。一旦不存在逆转对,
    // 则排序完毕。时间复杂度为O(N^2)。数据量较少时效果比较好。STL中提供了两个版本实现。版本一:以渐增方式排
    // 序,即以operator<为两个元素的比较函数;版本二:允许用户指定一个仿函数,作为两元素的比较函数。
    // 版本一实现
    template 
    void __insertion_sort(_RandomAccessIter __first, _RandomAccessIter __last) {
      if (__first == __last) return; 
      for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)   // 外循环
        // 以下,[first,i)形成一个子区间
        __linear_insert(__first, __i, __VALUE_TYPE(__first));
    }
    // 版本二实现
    template 
    void __insertion_sort(_RandomAccessIter __first,
                          _RandomAccessIter __last, _Compare __comp) {
      if (__first == __last) return;
      for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)
        __linear_insert(__first, __i, __VALUE_TYPE(__first), __comp);
    }
    template 
    void __unguarded_insertion_sort_aux(_RandomAccessIter __first, 
                                        _RandomAccessIter __last, _Tp*) {
      for (_RandomAccessIter __i = __first; __i != __last; ++__i)
        __unguarded_linear_insert(__i, _Tp(*__i));
    }
    template 
    inline void __unguarded_insertion_sort(_RandomAccessIter __first, 
                                    _RandomAccessIter __last) {
      __unguarded_insertion_sort_aux(__first, __last, __VALUE_TYPE(__first));
    }
    template 
    void __unguarded_insertion_sort_aux(_RandomAccessIter __first, 
                                        _RandomAccessIter __last,
                                        _Tp*, _Compare __comp) {
      for (_RandomAccessIter __i = __first; __i != __last; ++__i)
        __unguarded_linear_insert(__i, _Tp(*__i), __comp);
    }
    template 
    inline void __unguarded_insertion_sort(_RandomAccessIter __first, 
                                           _RandomAccessIter __last,
                                           _Compare __comp) {
      __unguarded_insertion_sort_aux(__first, __last, __VALUE_TYPE(__first),
                                     __comp);
    }
    

    final_insertion_sort函数

    此函数主要在排序算法达到基本有序后利用插入排序快速完成最终的排序

    // final_insertion_sort函数,首先判断元素个数是否大于16。如果答案为否,就调用__insertion_sort()加以处理
    // 如果答案为是,就将[first,last)分割为长度16的一段子序列,和另一段剩余子序列,再针对两个子序列分别调用
    // __insertion_sort()和__ungurarded_insertion_sort()。
    template 
    void __final_insertion_sort(_RandomAccessIter __first, 
                                _RandomAccessIter __last) {
      if (__last - __first > __stl_threshold) {
        __insertion_sort(__first, __first + __stl_threshold);
        __unguarded_insertion_sort(__first + __stl_threshold, __last);
      }
      else
        __insertion_sort(__first, __last);
    }
    
    template 
    void __final_insertion_sort(_RandomAccessIter __first, 
                                _RandomAccessIter __last, _Compare __comp) {
      if (__last - __first > __stl_threshold) {
        __insertion_sort(__first, __first + __stl_threshold, __comp);
        __unguarded_insertion_sort(__first + __stl_threshold, __last, __comp);
      }
      else
        __insertion_sort(__first, __last, __comp);
    }
    // 找出2^k<=n的最大值k。例:n=7,得k=2,n=20,得k=4,n=8,得k=3
    template 
    inline _Size __lg(_Size __n) {
      _Size __k;
      for (__k = 0; __n != 1; __n >>= 1) ++__k;
      return __k;
    }
    
  • 2)快速排序算法

    Quick Sort算法是目前最快的排序算法,平均复杂度为O(NlogN),最坏情况下将达到O(N^2)。不过IntroSort(类似median-of-three QuickSort的一种排序算法)可将最坏情况推进到O(NlogN)。

    Quick Sort算法叙述如下。假设s代表将被处理的序列:1)如果S的元素个数为0或1,结束;2)取S中的任何一个元素,当做枢轴(pivot)v;3)将S分割为L,R两段,使L内的每个元素都小于或等于v,R内的每一个元素都大于或等于v;4)对L,R递归执行Quick Sort。

    Quick Sort精神在于将大区间分割为小区间,分段排序。每一个小区间排序完成后,串接起来的大区间也就完成了排序。最坏情况发生在分割时产生出一个空的子区间——那完全没有达到分割的预期效果。

    Median-of-Three(三点中值算法)

    注意,任何一个元素都可以被选来当做枢轴,但是其合适与否却会影响Quick Sort的效率。为避免“元素当初输入时不够随机”所带来的恶化效应,最理想最稳当的方式就是取整个序列的头、尾、中央三个位置,以其中值作为枢轴。成为median-of-three partitioning或median-of-three Quick Sort。为了能够快速取出中央位置的元素,显然迭代器必须能够随机定位,即必为RandomAccessIterators。

    // Median-of-Three(三点中值):注意,任何一个元素都可以被选来当做枢轴,但是其合适与否却会影响
    // Quick Sort的效率。为避免“元素当初输入时不够随机”所带来的恶化效应,最理想最稳当的方式就是取整
    // 个序列的头、尾、中央三个位置,以其中值作为枢轴。成为median-of-three partitioning或median-of-three
    // Quick Sort。为了能够快速取出中央位置的元素,显然迭代器必须能够随机定位,即必为RandomAccessIterators。
    // 版本1
    template 
    inline const _Tp& __median(const _Tp& __a, const _Tp& __b, const _Tp& __c) {
      __STL_REQUIRES(_Tp, _LessThanComparable);
      if (__a < __b)
        if (__b < __c)            // a < b < c
          return __b;
        else if (__a < __c)       // a < b, b >= c, a < c
          return __c;
        else
          return __a;
      else if (__a < __c)         // c > a >= b
        return __a;
      else if (__b < __c)         // a >= b, a >= c, b < c
        return __c;
      else
        return __b;
    }
    // 版本2
    template 
    inline const _Tp&
    __median(const _Tp& __a, const _Tp& __b, const _Tp& __c, _Compare __comp) {
      __STL_BINARY_FUNCTION_CHECK(_Compare, bool, _Tp, _Tp);
      if (__comp(__a, __b))
        if (__comp(__b, __c))
          return __b;
        else if (__comp(__a, __c))
          return __c;
        else
          return __a;
      else if (__comp(__a, __c))
        return __a;
      else if (__comp(__b, __c))
        return __c;
      else
        return __b;
    }
    

    Partitioning(分割)算法

    即令头迭代器first向尾部移动,尾端迭代器last向头部移动。当*first大于或等于枢轴时就停下来,当*last小于或等于枢轴时也停下来,然后检验两个迭代器是否交错。如果first仍然在左而last仍然在右,就将两者元素互换,然后各自调整一个位置(向中央逼近),再继续进行相同的行为。如果发现两个迭代器交错了(即!(first < last)),表示整个序列已经调整完毕,此时的first为轴,将序列分为左右两半,左半部分所有元素都小于等于枢轴,右半部分所有元素值都大于等于枢轴。

    // 分割算法,主要使快排算法每次能够稳定分割,主要作为辅助函数
    // ForwardIter版本
    template 
    _ForwardIter __partition(_ForwardIter __first,
                     _ForwardIter __last,
                 _Predicate   __pred,
                 forward_iterator_tag) {
      if (__first == __last) return __first;      // 头指针等于为指针,所有操作结束
    
      while (__pred(*__first))
        if (++__first == __last) return __first;  // 头指针等于为指针,所有操作结束
    
      _ForwardIter __next = __first;
    
      while (++__next != __last)
        if (__pred(*__next)) {
          swap(*__first, *__next);
          ++__first;
        }
    
      return __first;
    }
    // BidirectionalIter版本
    template 
    _BidirectionalIter __partition(_BidirectionalIter __first,
                                   _BidirectionalIter __last,
                       _Predicate __pred,
                       bidirectional_iterator_tag) {
      while (true) {
        while (true)
          if (__first == __last)        // 头指针等于尾指针
            return __first;             // 所有操作结束
          else if (__pred(*__first))    // 头指针所指的元素符合不移动条件
            ++__first;                  // 不移动,头指针前进1
          else                          // 头指针符合元素移动条件
            break;                      // 跳出循环
        --__last;                       // 尾指针回溯1
        while (true)
          if (__first == __last)        // 头指针等于尾指针
            return __first;             // 所有操作结束
          else if (!__pred(*__last))    // 尾指针所指的元素符合不移动条件
            --__last;                   // 不移动,尾指针回溯1
          else                          // 尾指针所指元素符合移动条件
            break;                      // 跳出循环
        iter_swap(__first, __last);     // 头尾指针所指元素彼此交换
        ++__first;                      // 头指针前进1,准备下一个外循环迭代
      }
    }
    // RandomAccessIter版本
    template 
    _RandomAccessIter __unguarded_partition(_RandomAccessIter __first, 
                                            _RandomAccessIter __last, 
                                            _Tp __pivot) 
    {
      while (true) {
        while (*__first < __pivot)    // first找到>=pivot的元素就停下来
          ++__first;
        --__last;                     // 调整
        while (__pivot < *__last)     // last找到<=pivot的元素就停下来
          --__last;
        // 注意,以下first
    inline _ForwardIter partition(_ForwardIter __first,
                      _ForwardIter __last,
                      _Predicate   __pred) {
      __STL_REQUIRES(_ForwardIter, _Mutable_ForwardIterator);
      __STL_UNARY_FUNCTION_CHECK(_Predicate, bool, 
            typename iterator_traits<_ForwardIter>::value_type);
      return __partition(__first, __last, __pred, __ITERATOR_CATEGORY(__first));
    }
    

    IntroSort算法

    Insertion Sort在面对几近排序的序列时,表现出很好的排序功能,效果更好。sort算法中,使用混合排序算法:Introspective Sorting(内省式排序)IntroSort,其行为大部分情况下与median-of-3 Quick Sort完全相同,但是当分割行为有恶化为二次行为的倾向时,能够自我侦测,转为Heap Sort,使效率维持在Heap sort的O(NlogN),又比一开始使用Heap Sort效果好。

    // Insertion Sort算法:Insertion Sort以双层循环形式进行。外循环遍历整个序列,每次迭代决定出一个子区间;
    // 内循环遍历子区间内的每一个“逆转对”倒转过来。逆转对指任何两个迭代器i,j,i*j。一旦不存在逆转对,
    // 则排序完毕。时间复杂度为O(N^2)。数据量较少时效果比较好。STL中提供了两个版本实现。版本一:以渐增方式排
    // 序,即以operator<为两个元素的比较函数;版本二:允许用户指定一个仿函数,作为两元素的比较函数。
    // 版本一实现
    template 
    void __insertion_sort(_RandomAccessIter __first, _RandomAccessIter __last) {
      if (__first == __last) return; 
      for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)   // 外循环
        // 以下,[first,i)形成一个子区间
        __linear_insert(__first, __i, __VALUE_TYPE(__first));
    }
    // 版本二实现
    template 
    void __insertion_sort(_RandomAccessIter __first,
                          _RandomAccessIter __last, _Compare __comp) {
      if (__first == __last) return;
      for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)
        __linear_insert(__first, __i, __VALUE_TYPE(__first), __comp);
    }
    // 当元素个数为40时,__introsoft_loop()的最后一个参数将是5*2,意思是最多允许分割10层。
    // 版本一:本函数内的许多迭代器运算操作,都只适用于RandomAccess Iterators
    template 
    void __introsort_loop(_RandomAccessIter __first,
                          _RandomAccessIter __last, _Tp*,
                          _Size __depth_limit)
    {
      // 以下,__stl_threshold是个全局常数,稍早定义const int 16
      while (__last - __first > __stl_threshold) {    // >16
        if (__depth_limit == 0) {                     // 至此,分割恶化
          partial_sort(__first, __last, __last);      // 改用heapsort
          return;
        }
        --__depth_limit;
        // 以下是median-of-3 partition,选择一个够好的枢轴并决定分割点
        // 分割点将落在迭代器cut身上
        _RandomAccessIter __cut =
          __unguarded_partition(__first, __last,
                                _Tp(__median(*__first,
                                             *(__first + (__last - __first)/2),
                                             *(__last - 1))));
        // 对右半段递归进行sort
        __introsort_loop(__cut, __last, (_Tp*) 0, __depth_limit);
        __last = __cut;
        // 现在回到while循环,准备对左半段递归进行sort
        // 这种写法可读性较差,效率并没有比较好
      }
    }
    // 版本2
    template 
    void __introsort_loop(_RandomAccessIter __first,
                          _RandomAccessIter __last, _Tp*,
                          _Size __depth_limit, _Compare __comp)
    {
      while (__last - __first > __stl_threshold) {
        if (__depth_limit == 0) {
          partial_sort(__first, __last, __last, __comp);
          return;
        }
        --__depth_limit;
        _RandomAccessIter __cut =
          __unguarded_partition(__first, __last,
                                _Tp(__median(*__first,
                                             *(__first + (__last - __first)/2),
                                             *(__last - 1), __comp)),
           __comp);
        __introsort_loop(__cut, __last, (_Tp*) 0, __depth_limit, __comp);
        __last = __cut;
      }
    }
    
  • 3)sort算法

    STL中的sort算法前面主要利用IntroSort算法,最后使用InsertSort算法。

    // STL中sort算法
    // 版本1
    template 
    inline void sort(_RandomAccessIter __first, _RandomAccessIter __last) {
      __STL_REQUIRES(_RandomAccessIter, _Mutable_RandomAccessIterator);
      __STL_REQUIRES(typename iterator_traits<_RandomAccessIter>::value_type,
                     _LessThanComparable);
      if (__first != __last) {
        __introsort_loop(__first, __last,
                         __VALUE_TYPE(__first),
                         __lg(__last - __first) * 2);   // 用来控制恶化情况
        __final_insertion_sort(__first, __last);
      }
    }
    // 版本2
    template 
    inline void sort(_RandomAccessIter __first, _RandomAccessIter __last,
                     _Compare __comp) {
      __STL_REQUIRES(_RandomAccessIter, _Mutable_RandomAccessIterator);
      __STL_BINARY_FUNCTION_CHECK(_Compare, bool,
           typename iterator_traits<_RandomAccessIter>::value_type,
           typename iterator_traits<_RandomAccessIter>::value_type);
      if (__first != __last) {
        __introsort_loop(__first, __last,
                         __VALUE_TYPE(__first),
                         __lg(__last - __first) * 2,
                         __comp);
        __final_insertion_sort(__first, __last, __comp);
      }
    }
    
  • 参考文献

    STL源码剖析——侯捷

    STL源码

你可能感兴趣的:(STL源码剖析学习,STL源码阅读总结)