stl中sort分析

stl中最复杂庞大的算法无疑是sort,我当时在看这个算法时也费了不少时间,今天来总结一下,正好也复习一下部分排序算法.

  1. 该算法接受只两个RandomAccessIterators,然后将区间的所有元素以某种排序标准来排序(默认是递增排序).
    值得注意的是,stl中所有关系型容器都有自动排序功能(标准规定是map,set,multiset,multimap),所以也用不到sort.对于序列器stack,queue,priority-queue,这些都有特定的出入口,不允许用户对元素排序.
    那么我们也只剩下了vector,deque,list,前两者的迭代器是RandomAccessIterators,适合使用sort算法,list的迭代器属于BidiectioinalIterators,不在STL标准之列的slist,其迭代器更属于ForwardIterators,都不适合用sort算法,如果要对list和slist排序,应该使用他们的成员函数sort算法.

  2. 先说大体思路,sort算法在数据量大时采用Quick Sort,分段递归排序.一旦分段后的数据量小于某个门槛,为了避免Quick Sort的递归调用带来过大的额外符合,就改用Insertion Sort.如果递归层次过深,我们还会调用Heap Sort.

  3. 先说一下插入排序,先贴一下代码吧,然后我再根据书上的图片来进行解释.


    stl中sort分析_第1张图片
    image.png

    stl中sort分析_第2张图片
    image.png

    值得注意一下,这个函数在调用的过程中,在last前面的序列都是已经排序好的,这是我们写这个函数的基础,如果我们不知道这个情况,那么我们理解起来就会十分的费劲.先看第一种情况,如果value的值小于first的值,那么我们只需要将value放到第一位就OK了(因为first肯定是已经排序序列最小的值),值得一提的是我们使用的是copy_backward,具体原因大家可以自己想想.另外一种情况我们调用了__unguarded_linear_insert,也就是下面这个函数.

    stl中sort分析_第3张图片
    image.png

    这个函数实际上很好理解,因为我们只需要找到value应该被存放的合适的位置就可以了,因此我们只需要不断的左移last迭代器,直到找到一个值不大于value,然后在value放在该值后面的位置就可以了.


    stl中sort分析_第4张图片
    image.png

    这个图通过我上面的分析之后应该很好理解了,只是通过几个函数调用显得稍微复杂一点.

  4. Quick Sort
    因为插入排序的复杂度是O(N^2),所以面对大数据量,效率就不敢恭维了,在这里我不在叙述快排的原理了,因为我前面有篇博客专门讲解了快排的原理,还不熟悉的同学可以去看看那篇博客.

    • 下面说一下Median-of-Three,这个是用来选择快排枢轴的算法,思路是选择整个序列头,尾,中央三个位置的元素,以其中值作为枢轴,这种做法就叫三点中值快排算法.为了快速取出中央位置的元素,显然迭代器必须能够随机定位,也就是说必须是个RandomAccessIterators.


      stl中sort分析_第5张图片
      image.png

      代码就不上了,很简单的比较算法.

    • 分割
      我们定完了枢轴后,下面要做的就是把元素分为小于枢轴的和大于枢轴的,针对这个问题,下面叙述一个简单而又十分有成效的做法.令头端迭代器向尾部移动,尾端迭代器last向头部移动.当*first大于或者等于枢轴的时候就停下来,当*last小于或等于枢轴的时候也停下来,(个人认为两种情况下迭代器指向值等于枢轴的时候不停下来也是没有问题的)然后检验两个迭代器是否交错,如果未交错就将两个元素互换.如果交错了,说明序列已经调整完毕.此时,以first为轴,将序列分为左右两半,左边的元素都小于等于枢轴,右边的元素都大于等于枢轴.


      stl中sort分析_第6张图片

      stl中sort分析_第7张图片
      image.png

      stl中sort分析_第8张图片
      image.png

      我相信结合例子来看,这个算法也是不难理解的.

    • 阈值(threshold)
      从效率的角度来看,当元素数量很少的时候(例如只有十来个),使用快排这样比较复杂的算法是不太划算的,小数据量的情况下,插入排序也可能会快与快排,因为快排会产生的很多的函数递归调用.
    • final insertion sort
      对于几近排序但是尚未完成的序列,插入排序的表现非常好,所以我们在快排到了序列比较短的时候,改用插入排序而不是彻底排好序(从效率的角度考虑).
    • introsort
      不当的枢轴选择,导致不当的分割,导致快排恶化为O(N^2).而introsort则没这个问题,当分割没有问题时,它的表现和三点中值快排完全相同,但是当分割行为有恶化为二次行为的倾向时,能够自我侦测,转而使用Heap Sort,使效率维持在O(NLogN),又比一开始就使用Heap Sort来的好.
  5. sort
    下面是sort算法的源代码.


    stl中sort分析_第9张图片
    image.png

    关于__lg(),是用来控制分割恶化的情况.当元素个数为40时,__introsort_loop的最后一个参数是5*2,也就是10,代表最多分割四层.


    stl中sort分析_第10张图片
    image.png

    函数一开始就判断序列的大小,__stl_threshold默认是常数16,如果序列大小小于16,我们就等着调用插入排序.当大于16时,我们再检查分割层次,如果分割层次超过了指定值(例子中是10),说明我们的分割趋向于恶化,因为分割层次过深,这个指定值的选择是有一定含义在内的,超过了分割层次我们就调用partial sort,也就是堆排序.
    如果这些都没问题,我们就开始进入快排的阶段了,找出分割点,然后对左右段落递归进行IntroSort.当introsort结束之后,显然我们得到的是很多"元素个数少于16"的子序列,每个子序列都有相当程度的排序,但尚未完全排序.然后我们再回到主函数,下一步调用__final_insertion_sort()
    stl中sort分析_第11张图片
    image.png

    这个函数先判断元素个数是否大于16,如果答案为否,我们就调用__insertion_sort来进行处理.如果答案是肯定的,就将[first,last)分割为长度为16的子序列,和另一段剩余子序列,再针对两个子序列分别调用__insertion_sort()和__unguarded_insertion_sort().前者的代码前面已经有了,下面展示后面的代码:


    stl中sort分析_第12张图片
    image.png

    之所以我们在最后没有调用quicksort的原因是,到了这一步,整个序列已经基本被排列好了,也就是排序程度很高,但没有完全排序,所以在这里我们只用了插入排序,这样的效率是高于快排的.到这里,sort算法就讲的差不多了.

结语:写到这里,sort算法也算是说的差不多了,确实是一个相当复杂的一个算法,用到了很多的优化技巧以及为了效率"无所不用其极",当然为了效率,这些都是值得.排序算法是我们生活中非常常用的一个算法,我们要做到熟悉常见的排序算法,还要对其复杂度以及应用场景做到很熟悉,这样在实际code的过程中就可以做到游刃有余的应用了.

你可能感兴趣的:(stl中sort分析)