STL中的sort源码分析

1 篇文章 0 订阅

title: STL中的sort源码分析
date: 2020-12-08 20:43:11
tags: STL | source code
categories: C++ | STL


前言

本文采用gcc5.4.0版本的STL进行分析,链接:在线源码。在下文的分析中都具体指出了对应文件的多少行,方便读者对照查看。如你想要在自己电脑上查看源码,相信你已经安装了gcc,STL绝大多数源码都位于/usr/include/c++/10.2.0/bits目录下。

我们都听说sort()内部是使用快速排序,但是实际上为了适应可能出现的各种情况,STL采用了一系列的优化策略以及算法来实现sort(),而不是单纯地只通过快排实现。本篇文章从源码角度解读std::sort()函数。

在下面的分析过程中,每行代码都指明了出自哪个文件第几行,代码旁边也配有较为详细的注释,相信对你会有帮助。

_Compare比较器

sort()算法提供两种接口,一是接收两个RandomAccessIterator, first, last(随机访问迭代器,支持算术加减操作),默认对[fast, last)内的元素进行升序操作,比较操作是通过operator < 进行的;另一种接口是在额外接收一个函数指针或者仿函数(functor)来作为比较的规则,函数返回类型必须是可以转化为bool类型的类型。接下来便正式看一看sort源码。

sort算法位于stl_algo.h文件内,这个文件比较大,几乎包含了stl中所有算法。stl_algo.h源码

 // 位于stl_algo.h文件中
 4687   template<typename _RandomAccessIterator>
 4688     inline void
 4689     sort(_RandomAccessIterator __first, _RandomAccessIterator __last)
 4690     {
 4691       // concept requirements
 4692       __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
 4693             _RandomAccessIterator>)
 4694       __glibcxx_function_requires(_LessThanComparableConcept<
 4695             typename iterator_traits<_RandomAccessIterator>::value_type>)
 4696       __glibcxx_requires_valid_range(__first, __last);
 4697 
 4698       std::__sort(__first, __last, __gnu_cxx::__ops::__iter_less_iter());
 4699     }
 ......
 4716   template<typename _RandomAccessIterator, typename _Compare>
 4717     inline void
 4718     sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
 4719          _Compare __comp)
 4720     {
 4721       // concept requirements
 4722       __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
 4723             _RandomAccessIterator>)
 4724       __glibcxx_function_requires(_BinaryPredicateConcept<_Compare,
 4725             typename iterator_traits<_RandomAccessIterator>::value_type,
 4726             typename iterator_traits<_RandomAccessIterator>::value_type>)
 4727       __glibcxx_requires_valid_range(__first, __last);
 4728 
 4729       std::__sort(__first, __last, __gnu_cxx::__ops::__iter_comp_iter(__comp));
 4730     }

上方代码中,便展示了sort的两种接口,可以看到最后是调用std::__sort(),在这之前你肯定会好奇__gnu_cxx::__ops::__iter_less_iter()__gnu_cxx::__ops::__iter_comp_iter(__comp)是什么,在predifined_ops.h中可以找到如下代码,分析可以知道:

  1. __iter_less_iter()是一个函数,它返回一个临时对象_Iter_less_iter()这个临时对象是一个funtor仿函数,因为它内部重载了operator(),所以可以表现得像一个普通的函数。当要判断pa指向的元素和pb指向的元素的"大小"时,便可以使用 __Iter_less_iter(pa, pb),函数return *pa < *pb;

  2. 同样__iter_comp_iter(_Compare __comp)是个接收参数为比较器__comp的函数。(前面说过比较器也是一个仿函数或者函数指针,为了叙述简洁后文不再提及这一点,读者应牢记这一点)这个函数它返回一个临时对象_Iter_comp_iter<_Compare>(__comp),临时对象的内部比较器成员_M_comp被初始化为__comp。

    小结一下,std::__sort()接收的第3个参数是一个functor,functor决定比较规则。

 // 位于predifined_ops.h文件中
   33 namespace __gnu_cxx
   34 {
   35 namespace __ops
   36 {
   37   struct _Iter_less_iter			// 类名
   38   {
   39     template<typename _Iterator1, typename _Iterator2>
   40       _GLIBCXX14_CONSTEXPR
   41       bool
   42       operator()(_Iterator1 __it1, _Iterator2 __it2) const	//重载operator()
   43       { return *__it1 < *__it2; }
   44   };
   45   _GLIBCXX14_CONSTEXPR
   46   inline _Iter_less_iter
   47   __iter_less_iter()
   48   { return _Iter_less_iter(); }		// 返回一个_Iter_less_iter类型的临时对象
	......
     
  110   template<typename _Compare>
  111     struct _Iter_comp_iter
  112     {
  113       _Compare _M_comp;		// 成员变量
  114       _GLIBCXX14_CONSTEXPR
  115       _Iter_comp_iter(_Compare __comp)	// 构造函数
  116         : _M_comp(__comp)					// 初始化成员
  117       { }
  118 
  119       template<typename _Iterator1, typename _Iterator2>
  120         _GLIBCXX14_CONSTEXPR
  121         bool
  122         operator()(_Iterator1 __it1, _Iterator2 __it2)	// 成员函数operator()
  123         { return bool(_M_comp(*__it1, *__it2)); }
  124     };
  125 
  126   template<typename _Compare>
  127     _GLIBCXX14_CONSTEXPR
  128     inline _Iter_comp_iter<_Compare>
  129     __iter_comp_iter(_Compare __comp)
  130     { return _Iter_comp_iter<_Compare>(__comp); }

__sort()

 // 位于stl_algo.h文件中
 1954   // sort
 1955 
 1956   template<typename _RandomAccessIterator, typename _Compare>
 1957     inline void
 1958     __sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
 1959            _Compare __comp)
 1960     {
 1961       if (__first != __last)
 1962         {
 1963           std::__introsort_loop(__first, __last,
 1964                                 std::__lg(__last - __first) * 2,
 1965                                 __comp);
 1966           std::__final_insertion_sort(__first, __last, __comp);
 1967         }
 1968     }

__sort代码看起来很清楚

  1. 三个参数:__first和__last是传入的迭代器,__comp是默认的比较器或者是我们自定义的比较器。
  2. 代码逻辑:如果[first, last)区间有元素即不为空时,调用一次std::__introsort_loop()函数,然后再调用一次std::__final_insertion_sort()函数。
  3. 返回值为void

再研究两个子函数前,先了解下std::__lg(__last - __first) * 2的作用: __lg(n)返回2k <= n的最大k值。后文会讲到,它是用来控制分割恶化的情况。

std::__introsort_loop()

std::__introsort_loop()是sort的重头戏,阅读下方代码及注释:

  // 位于stl_algo.h文件中

 1866   /*
 1867    *  @doctodo
 1868    *  This controls some aspect of the sort routines.
 1869    */
 1870   enum { _S_threshold = 16 };

 1932   /// This is a helper function for the sort routine.
 1933   template<typename _RandomAccessIterator, typename _Size, typename _Compare>
 1934     void
 1935     __introsort_loop(_RandomAccessIterator __first,
 1936                      _RandomAccessIterator __last,
 1937                      _Size __depth_limit, _Compare __comp)
 1938     {
 1939       while (__last - __first > int(_S_threshold))	// 如果序列长度>16,进入循环
 1940         {
     			// 循环__depth_limit次后,对[first, last)使用__partial_sort进行排序
     			// 至此,整个序列基本有序,返回
 1941           if (__depth_limit == 0)		
 1942             {
 1943               std::__partial_sort(__first, __last, __last, __comp);
 1944               return;
 1945             }
     
 1946           --__depth_limit;			
     			
     			// 选出一个合适的中枢值作为分割点
 1947           _RandomAccessIterator __cut =
 1948             std::__unguarded_partition_pivot(__first, __last, __comp);
     			
     			// 对右半段进行排序
 1949           std::__introsort_loop(__cut, __last, __depth_limit, __comp);
 1950           
     			// 现在右半段已经有序,回到左半段,重新进入while循环进行递归排序
     			__last = __cut;
 1951         }
 1952     }

__introsort_loop算法解释

看完上面的代码可以得出一个结论:经过__introsort_loop后,整个序列大体上是有序的。这是因为这个算法是对无序部分的右半段进行排序,进行多少次呢?进行__depth_limit次前提是要排序的长度一直大于16,__depth_limit等于2倍的lg(n),进行__depth_limit次操作后,剩下的无序的左半部分直接使用__partial_sort()进行排序。

这里就涉及几个细节:

  1. 如何划分左半段右半段,由__unguarded_partition_pivot实现。
  2. 最后一小段无序的左半部分排序由__partial_sort实现。

__unguarded_partition_pivot和__partial_sort

__unguarded_partition_pivot由两个部分组成:三中值法,将*first的值交换为*(first+1),*mid,*(last - 1)中的中间值; 进行partition,返回值cut左边的全小于等于中枢值,右边全大于等于中枢值。

 // 位于stl_algo.h文件中

 1909   /// This is a helper function...
 1910   template<typename _RandomAccessIterator, typename _Compare>
 1911     inline _RandomAccessIterator
 1912     __unguarded_partition_pivot(_RandomAccessIterator __first,
 1913                                 _RandomAccessIterator __last, _Compare __comp)
 1914     {
 1915       _RandomAccessIterator __mid = __first + (__last - __first) / 2;
     		
     		//将*(first + 1), *mid, *(last - 1)中的中间值交换到first的位置
 1916       std::__move_median_to_first(__first, __first + 1, __mid, __last - 1,
 1917                                   __comp);
     		
 1918       return std::__unguarded_partition(__first + 1, __last, __first, __comp);
 1919     }


__move_median_to_first__unguarded_partition源码:

 	// 位于stl_algo.h文件中

   75   /// Swaps the median value of *__a, *__b and *__c under __comp to *__result
   76   template<typename _Iterator, typename _Compare>
   77     void
   78     __move_median_to_first(_Iterator __result,_Iterator __a, _Iterator __b,
   79                            _Iterator __c, _Compare __comp)
   80     {
   81       if (__comp(__a, __b))		// 如果*a < *b
   82         {
   83           if (__comp(__b, __c))	// 如果*b < *c
   84             std::iter_swap(__result, __b);	//此时b是中间值,交换*result和*b的值
   85           else if (__comp(__a, __c))
   86             std::iter_swap(__result, __c);
   87           else
   88             std::iter_swap(__result, __a);
   89         }
   90       else if (__comp(__a, __c))
   91         std::iter_swap(__result, __a);
   92       else if (__comp(__b, __c))
   93         std::iter_swap(__result, __c);
   94       else
   95         std::iter_swap(__result, __b);
   96     }

 // 位于stl_algo.h文件中
 // 这里就是我们熟悉的partition过程了
 1888   /// This is a helper function...
 1889   template<typename _RandomAccessIterator, typename _Compare>
 1890     _RandomAccessIterator
 1891     __unguarded_partition(_RandomAccessIterator __first,
 1892                           _RandomAccessIterator __last,
 1893                           _RandomAccessIterator __pivot, _Compare __comp)
 1894     {
 1895       while (true)
 1896         {
 1897           while (__comp(__first, __pivot))	// first++ 直到*first >= *pivot
 1898             ++__first;						// 退出循环时first为第一个>=pivot的下标
     			
 1899           --__last;	// 让last指向序列的最后一个元素
     
 1900           while (__comp(__pivot, __last))		// last-- 直到*last <= *pivot
 1901             --__last;							// 退出循环时last为第一个<=pivot的下标
     				
     			// 如果两个迭代器交叉了,说明整个序列已经调整完毕,
     			// first左边的全 <=pivot, 右边的都 >=pivot 
 1902           if (!(__first < __last))		
 1903             return __first;				// 返回此时的first
     
     			// 交换,从这里可以看出,这个算法是不稳定的,因为*first 可能等于 *last
 1904           std::iter_swap(__first, __last);
     			
 1905           ++__first;					// 交换后,first后移一位
 1906         }
 1907     }

__partial_sort是通过堆排序来实现的,这里涉及到STL中的内建堆,如果像上面一样深究到每一行代码,涉及到的源代码会非常多,因此这里不过分展开,知道__partial_sort实际上是堆排序即可。

 // 位于stl_algo.h文件中
 1921   template<typename _RandomAccessIterator, typename _Compare>
 1922     inline void
 1923     __partial_sort(_RandomAccessIterator __first,
 1924                    _RandomAccessIterator __middle,
 1925                    _RandomAccessIterator __last,
 1926                    _Compare __comp)
 1927     {
 1928       std::__heap_select(__first, __middle, __last, __comp);
 1929       std::__sort_heap(__first, __middle, __comp);
 1930     }

 // 位于stl_algo.h文件中
 1662   /// This is a helper function for the sort routines.
 1663   template<typename _RandomAccessIterator, typename _Compare>
 1664     void
 1665     __heap_select(_RandomAccessIterator __first,
 1666                   _RandomAccessIterator __middle,
 1667                   _RandomAccessIterator __last, _Compare __comp)
 1668     {
 1669       std::__make_heap(__first, __middle, __comp);
 1670       for (_RandomAccessIterator __i = __middle; __i < __last; ++__i)
 1671         if (__comp(__i, __first))
 1672           std::__pop_heap(__first, __middle, __i, __comp);
 1673     }


 // 位于stl_heap.h文件中
  388   template<typename _RandomAccessIterator, typename _Compare>
  389     void
  390     __sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
  391                 _Compare __comp)
  392     {
  393       while (__last - __first > 1)
  394         {
  395           --__last;
  396           std::__pop_heap(__first, __last, __last, __comp);
  397         }
  398     }

__introsort_loop()小结

根据中枢值划分左右部分,对左右部分进行递归__introsort_loop排序,当递归层数大于lg(n) * 2时进行堆排序;当序列长度小于_S_threshold即16时,返回。

最终__introsort_loop结束时,[fast, last)基本有序,可以看成由若干个长度小于_S_threshold的有序序列构成。

std::__final_insertion_sort

通过上述分析可以知道,此时序列已经基本有序了,而插入排序对于整体有序的序列进行排序效率是非常高的。

判断序列长度是否大于_S_threshold即16:

如果大于,将序列分成两段,一段长度为16,对其调用__insertion_sort,另一段调用__unguarded_insertion_sort()

否则,直接调用__insertion_sort()

具体细节就看源代码和注释了,如下:

  // 位于stl_algo.h文件中

 1872   /// This is a helper function for the sort routine.
 1873   template<typename _RandomAccessIterator, typename _Compare>
 1874     void
 1875     __final_insertion_sort(_RandomAccessIterator __first,
 1876                            _RandomAccessIterator __last, _Compare __comp)
 1877     {
 1878       if (__last - __first > int(_S_threshold))
 1879         {
 1880           std::__insertion_sort(__first, __first + int(_S_threshold), __comp);
 1881           std::__unguarded_insertion_sort(__first + int(_S_threshold), __last,
 1882                                           __comp);
 1883         }
 1884       else
 1885         std::__insertion_sort(__first, __last, __comp);
 1886     }

     		// 下面注释摘自move.h 第56行,152~157行,
			//如果你不了解move语义及右值引用,请自行查阅资料,这里不详细介绍
   			//56 #if __cplusplus >= 201103L
  			//152 #define _GLIBCXX_MOVE(__val) std::move(__val)
  			//153 #define _GLIBCXX_FORWARD(_Tp, __val) std::forward<_Tp>(__val)
  			//154 #else
  			//155 #define _GLIBCXX_MOVE(__val) (__val)
  			//156 #define _GLIBCXX_FORWARD(_Tp, __val) (__val)
  			//157 #endif


 1832   /// This is a helper function for the sort routine.
 1833   template<typename _RandomAccessIterator, typename _Compare>
 1834     void
 1835     __insertion_sort(_RandomAccessIterator __first,
 1836                      _RandomAccessIterator __last, _Compare __comp)
 1837     {
 1838       if (__first == __last) return;
 1839 
 1840       for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i)
 1841         {
 1842           if (__comp(__i, __first))	
     			//如果*i 比第一个*first还要小,就不需要比较,直接把*i插到最前面即可
 1843             {
     				// 萃取出_RandomAccessIterator指向元素的value_type
 1844               typename iterator_traits<_RandomAccessIterator>::value_type
     				
     				// 赋值, 窃取*__i中的资源
 1845                 __val = _GLIBCXX_MOVE(*__i);
     
     				// 摘自stl_algobase.h 689行,C++11中
     				//#define _GLIBCXX_MOVE_BACKWARD3(_Tp, _Up, _Vp) 
     				//  std::move_backward(_Tp, _Up, _Vp)
 1846               _GLIBCXX_MOVE_BACKWARD3(__first, __i, __i + 1);
     				
 1847               *__first = _GLIBCXX_MOVE(__val);
 1848             }
 1849           else
 1850             std::__unguarded_linear_insert(__i,
 1851                                 __gnu_cxx::__ops::__val_comp_iter(__comp));
 1852         }
 1853     }

 1855   /// This is a helper function for the sort routine.
 1856   template<typename _RandomAccessIterator, typename _Compare>
 1857     inline void
 1858     __unguarded_insertion_sort(_RandomAccessIterator __first,
 1859                                _RandomAccessIterator __last, _Compare __comp)
 1860     {
 1861       for (_RandomAccessIterator __i = __first; __i != __last; ++__i)
     		  // 从i开始,下标0~i-1元素已经有序,将i插入到合适的位置
 1862         std::__unguarded_linear_insert(__i,
 1863                                 __gnu_cxx::__ops::__val_comp_iter(__comp));
 1864     }


 1813   /// This is a helper function for the sort routine.
 1814   template<typename _RandomAccessIterator, typename _Compare>
 1815     void
 1816     __unguarded_linear_insert(_RandomAccessIterator __last,
 1817                               _Compare __comp)
 1818     {
 1819       typename iterator_traits<_RandomAccessIterator>::value_type

 1820         __val = _GLIBCXX_MOVE(*__last); // 窃取*__last内的资源
 1821       _RandomAccessIterator __next = __last;
 1822       --__next;
 1823       while (__comp(__val, __next))	// val 比它前面的元素*next还要小
 1824         {
 1825           *__last = _GLIBCXX_MOVE(*__next);	
 1826           __last = __next;
 1827           --__next;					// next左移,直至val >= *next
 1828         }								
 1829       *__last = _GLIBCXX_MOVE(__val);	// last就是val应该待的位置
 1830     }


  // 位于predifined_ops.h文件中

  // 类似iter_less_iter()函数,返回一个类型为_Val_comp_iter<_Compare>的functor
  172   template<typename _Compare>
  173     inline _Val_comp_iter<_Compare>
  174     __val_comp_iter(_Compare __comp)
  175     { return _Val_comp_iter<_Compare>(__comp); }	



  157   template<typename _Compare>
  158     struct _Val_comp_iter
  159     {
  160       _Compare _M_comp;		// 成员变量
  161 
  162       _Val_comp_iter(_Compare __comp)		// 构造函数
  163         : _M_comp(__comp)
  164       { }
  165 
  166       template<typename _Value, typename _Iterator>
  167         bool
  168         operator()(_Value& __val, _Iterator __it)		// 重载operator()
  169         { return bool(_M_comp(__val, *__it)); }
  170     };

小结

std::sort()有两种接口,一种支持用户自定义比较器,两种最后都是调用__sort(),接收3个参数,如下:

 __sort(_RandomAccessIterator __first, _RandomAccessIterator,
        _Compare __comp)

__sort()函数主要有两大步骤:

  1. 进行__introsort_loop(),这一步是精华所在,根据中枢值划分左右部分,对左右部分进行递归__introsort_loop排序,当递归层数大于lg(n) * 2时进行堆排序,其中n为总序列长度;递归时,当序列长度小于_S_threshold即16时,返回。最终__introsort_loop结束时,[fast, last)基本有序,可以看成由若干个长度小于_S_threshold的有序序列构成。

  2. 经过上步后,序列整体有序,再进行__final_insertion_sort,插入排序里面又有很多优化,具体见上述代码注解分析,这里不再赘述。

结语

每次看源码时间耗时短,写博客耗费时间长。虽然追踪解析源码的过程挺有趣的,但是到后面写了好几个小时博客的时候,效率就变得很低了,越写越慢,希望这篇文章对你有帮助!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值