vs2013 std::sort 分析

由于之前在debug模式下发现stl的sort简直慢到不能忍,所以自己写了一个sgi的sort,后来发现在release模式下,vs自带的sort快的不行,就研究了下。

这里有些和sgi-stl相通的东西就简略带过了,详细内容可以看我之前的stl源码的笔记:

sgi-sort_link

首先来看下大概的过程:

  1.没有调用到一定深度时,就进行划分并进行递归调用。
  2.如果超过了一定深度时,这个区间改为调用堆排序。(这一部待商榷)
  3.对剩下的小于32长度的区间进行插入排序。

接下来是详细分析:

1:当我们调用sort(frist,last);时,程序就来到下面的代码,帮忙添加一个参数:less<>()默认用<来比较。

template<class _RanIt> inline
    void sort(_RanIt _First, _RanIt _Last)
    {   // order [_First, _Last), using operator<
    _STD sort(_First, _Last, less<>());
    }

2:接着来到,这时会调用真正的sort,也就是_Sort

template<class _RanIt,
    class _Pr> inline
    void sort(_RanIt _First, _RanIt _Last, _Pr _Pred)
    {   // order [_First, _Last), using _Pred
    _DEBUG_RANGE(_First, _Last);
    _DEBUG_POINTER(_Pred);
    _Sort(_Unchecked(_First), _Unchecked(_Last), _Last - _First, _Pred);
    }

3:然后进入真正的sort:这里进行说明

template<class _RanIt,
    class _Diff,
    class _Pr> inline
    void _Sort(_RanIt _First, _RanIt _Last, _Diff _Ideal, _Pr _Pred)
    {   // order [_First, _Last), using _Pred
    _Diff _Count;
        //_Count是计算出来的传入的元素个数,貌似为了安全,还要传入一个元素个数的参数
        //也就是_Ideal
    for (; _ISORT_MAX < (_Count = _Last - _First) && 0 < _Ideal; )
        {   // divide and conquer by quicksort
            // 这个循环的原因是为了继续划分
        pair<_RanIt, _RanIt> _Mid =
            _Unguarded_partition(_First, _Last, _Pred);
        _Ideal /= 2, _Ideal += _Ideal / 2;  // allow 1.5 log2(N) divisions

        //如果左边区间比较小就递归排序左边的区间,然后继续划分右边的区间
        //否则排序右边,划分左边
        if (_Mid.first - _First < _Last - _Mid.second)
            {   // loop on second half
            _Sort(_First, _Mid.first, _Ideal, _Pred);
            _First = _Mid.second;
            }
        else
            {   // loop on first half
            _Sort(_Mid.second, _Last, _Ideal, _Pred);
            _Last = _Mid.first;
            }
        }
        //划分到区间小于_ISORT_MAX 

    //深度太深,改为堆排序。为什么我感觉这是永远不会执行到的因为_ISORT_MAX <_Count
    //不是上一个循环的结束判断条件么
    //而且在release模式下,我设断点都会直接跳过,并不执行if的判断
    //所以目测是不会执行的
    if (_ISORT_MAX < _Count)
        {   // heap sort if too many divisions
        _STD make_heap(_First, _Last, _Pred);
        _STD sort_heap(_First, _Last, _Pred);
        }
    else
        if (1 < _Count)//到了底层,就调用插入排序了,奇怪的是为什么不学sgi的插入排序,可以
                        //节省很多次的判断。
        _Insertion_sort(_First, _Last, _Pred);  // small
    }

4:下面看看重头戏:

    template<class _RanIt,
    class _Pr> inline
    pair<_RanIt, _RanIt>
        _Unguarded_partition(_RanIt _First, _RanIt _Last, _Pr _Pred)
    {   // partition [_First, _Last), using _Pred

    //找到最中间的元素。
    _RanIt _Mid = _First + (_Last - _First) / 2;

    //_Median这个函数的作用是把头中尾这三个数进行排序,还会把头中尾附近三个左右的元素进行排序
    //具体不必关心,这里只要知道头中尾三个元素是排好序的。而且_Mid就是关键划分元素
    _Median(_First, _Mid, _Last - 1, _Pred);

    //_Pfirst 是左半部分的last,_Plast 是右半部分的first值
    //这里虽然看起来first和last颠倒了,其实这两个值的意思是[_Pfirst, _Plast)区间的元素值
    //全部是_Mid的值("_Mid指向的值"下面简称:mid);
    _RanIt _Pfirst = _Mid;
    _RanIt _Plast = _Pfirst + 1;

    //有重复的值就左移或右移动来扩大区间最后达到的效果是这样的[_Pfirst..一堆mid.._Plast)
    //注意_Pfirst和_Plast-1指向的值也是mid
    while (_First < _Pfirst
        && !_DEBUG_LT_PRED(_Pred, *(_Pfirst - 1), *_Pfirst)
        && !_Pred(*_Pfirst, *(_Pfirst - 1)))
        --_Pfirst;
    while (_Plast < _Last
        && !_DEBUG_LT_PRED(_Pred, *_Plast, *_Pfirst)
        && !_Pred(*_Pfirst, *_Plast))
        ++_Plast;

    //这两个值分别是未划分的左边部分的尾部:_Glast 以及右半部分的头部:_Gfirst
    _RanIt _Gfirst = _Plast;
    _RanIt _Glast = _Pfirst;

    //这时完整的区间是这样的[_First....[_Pfirst(同时也是_Glast)..._Plast)
    //...._Last)!!注意这里是开区间哈   _Plast也是_Gfirst,为了好看,就写在这了
    for (; ; )
        {   // 划分开始了。。。
        for (; _Gfirst < _Last; ++_Gfirst)//先进行边界判断
        {
            //从右半边开始,只要_Gfirst指向的值(下面指向的值都去掉“指向的值”这四个字来简称)
            //比_Pfirst(也就是mid)大就一直++
            //直到找到比_Pfirst小的值这样就可以和_Pfirst-1的元素进行交换了
            if (_DEBUG_LT_PRED(_Pred, *_Pfirst, *_Gfirst))
                ;
            else if (_Pred(*_Gfirst, *_Pfirst))//找到
                break;

            //一个小插曲:如果找到和mid一样大的值就把这个值换到_Plast++的位置来保持
            //[_Pfirst, _Plast)区间全是mid;
            else if (_Plast++ != _Gfirst)//如果两个位置相同就不用换了
                _STD iter_swap(_Plast - 1, _Gfirst);
        }

        //同上面,相应的左区间的操作,最后找到大于mid的位置。
        //只要注意stl右边都是开区间的,_Glast-1才是前面区间的最后一个值
        for (; _First < _Glast; --_Glast)
        {
            if (_DEBUG_LT_PRED(_Pred, *(_Glast - 1), *_Pfirst))
                ;
            else if (_Pred(*_Pfirst, *(_Glast - 1)))
                break;
            else if (--_Pfirst != _Glast - 1)
                _STD iter_swap(_Pfirst, _Glast - 1);
        }

        //达到边界就返回一个pair指向mid区间
        //这里的判断是这样的:
        //1:_Glast 是左边未划分区间的最后一个元素的后一个位置所以_First之前
        //未划分_First开始,已经划分。
        //2:_Gfirst 代表右边未划分的第一个元素,而_Last是我们需要划分的区间的最后一个
        //元素的后一个位置,所以如果下面条件成立,[_First ,_Last)划分完成
        if (_Glast == _First && _Gfirst == _Last)
            return (pair<_RanIt, _RanIt>(_Pfirst, _Plast));

        //左边全部划分完了,看起来像这样[_First(_Glast,)..._Pfirst..............)
        //这时如果要交换左右两边的元素就需要特殊处理了
        if (_Glast == _First)
            {   // no room at bottom, rotate pivot upward

            //情况1:如果_Plast ==_Gfirst说明_Gfirst就是mid区间的last位置
            //这时只需要交换_Pfist和_Gfirst就可以把mid区间右移一格
            if (_Plast != _Gfirst)
                _STD iter_swap(_Pfirst, _Plast);
                //情况2:先把一个mid换到最后面,这时_Pfirst指向一个大于mid的值,_Gfirst是指向小于mid的值的。

            //++_Plast, 因为中间全是mid的区间要整个后移一格
            ++_Plast;

            //情况1:交换_Pfist和_Gfirst相当于把第一个mid值和右边大于mid的值交换
            //情况2:交换_Pfirst,_Gfirst,刚好把小的换到左边,大的换到右边
            _STD iter_swap(_Pfirst++, _Gfirst++);
            }
        else if (_Gfirst == _Last)
            {   // no room at top, rotate pivot downward
            if (--_Glast != --_Pfirst)
                _STD iter_swap(_Glast, _Pfirst);
            _STD iter_swap(_Pfirst, --_Plast);
            }
        else

            //交换左右区间元素。
            _STD iter_swap(_Gfirst++, --_Glast);
        }

        //这个其实就是双基准快排的实现,相对于普通快排最后得到的结果就是把_Mid元素都集中在
        //一起了.
    }

到此完结,能看到这相信你肯定已经懂了哈,有不对的地方希望大神指出,谢谢!

时间: 2024-10-15 05:06:05

vs2013 std::sort 分析的相关文章

非常无聊——STD::sort VS 基数排序

众所周知,Std::sort()是一个非常快速的排序算法,它基于快排,但又有所修改.一般来说用它就挺快的了,代码一行,时间复杂度O(nlogn)(难道不是大叫一声"老子要排序!!"就排好了么...).我们也知道,不基于比较的排序可以达到O(n),比如说基数排序.什么,它是O(n * log(10)( max(n) ) ) 的?NO!!我们可以用sqrt(max(n))来作为进制,这样就是(N*logMax(n))=O(2*n)的了..看起来很不错, 代码量嘛....呵呵 所谓基数排序,

stl sort分析

最近写代码,无意中发现了一个坑,关于自定义比较函数的stl sort函数的坑,于是记录下来. 先贴代码: 1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 5 struct finder 6 { 7 bool operator()(int first, int second){return first <= second;} 8 } my_finder; 9 10 int main

GDB实践:一场std::sort引发的coredump

以前只掌握gdb一些基础知识,还没有真正"实战"过.刚好最近同事一个进程coredump了,原因比较深,正好利用这个机会来分析下 // @ 运行:gdb [可执行程序] -c [coredump文件] gdb edu_info_recommend_svr -c core_edu_info_recomm // @ 查看堆栈信息:bt (backtrace) (gdb) bt 10 #0 0x00007fa0809b6144 in __strcmp_sse42 () from /lib64

std::sort引发的core

#include <stdio.h> #include <vector> #include <algorithm> #include <new> struct foo_t { int size; }; class cmp_t { public: bool operator()(foo_t *a, foo_t *b) { return a->size >= b->size; } }; int main(int argc, char *argv

std::sort源码剖析

文章参考:源码 这篇文章在一个偶然的机会看到,我原先也是知道sort函数效率高,但终究没有去了解原因,读了这篇文章更加钦佩C++大师积年累月智慧的结晶和对效率的极致追求,看到很多地方不禁暗暗称奇.也还是感慨原文作者对技术的追求和细致的讲解,下面的内容大多来自作者的文章,其中加入了自己的理解,也不枉费大半个下午的时间. 从事程序设计行业的朋友一定对排序不陌生,它从我们刚刚接触数据结构课程开始便伴随我们左右,是需要掌握的重要技能.任何一本数据结构的教科书一定会介绍各种各样的排序算法,比如最简单的冒泡

std::sort 学习:一种递归分治方法

// std::sort 学习:一种递归分治方法 今天看了看 stl 的 std::sort 的代码,众所周知,这个函数是在快速排序递归太深的时候使用堆排序防止过度退化,但是今天说的不是这个.我们只看快速排序的部分. 我们一般实现快速排序大概是这样的(本王随意写了个用下标当参数的排序函数,领会意思即可). void quick_sort(int first, int last) // 某个数组的 [first, last) {  if ((last - first) > 1) {  int mi

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

std::sort要求strict weak ordering

strict weak ordering简单地说就是小于语义,非小于等于语义,也就是说对于相等的或者异常的元素比较应当返回false 后果很严重,在google搜一下violating strict weak ordering make std::sort crash能看到很多种后果, 经测试,当待排序元素大于16个时使用std::sort,若传入的callable违反strict weak ordering,则可能死循环也可能越界访问. 待排序元素小于等于16个不会有问题是因为std::sor

shuffle和sort分析

MapReduce中的Shuffle和Sort分析 MapReduce 是现今一个非常流行的分布式计算框架,它被设计用于并行计算海量数据.第一个提出该技术框架的是Google 公司,而Google 的灵感则来自于函数式编程语言,如LISP,Scheme,ML 等.MapReduce 框架的核心步骤主要分两部分:Map 和Reduce.当你向MapReduce 框架提交一个计算作业时,它会首先把计算作业拆分成若干个Map 任务,然后分配到不同的节点上去执行,每一个Map 任务处理输入数据中的一部分