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中可以找到如下代码,分析可以知道:
-
__iter_less_iter()
是一个函数,它返回一个临时对象_Iter_less_iter()
,这个临时对象是一个funtor仿函数,因为它内部重载了operator()
,所以可以表现得像一个普通的函数。当要判断pa指向的元素和pb指向的元素的"大小"时,便可以使用 __Iter_less_iter(pa, pb),函数return *pa < *pb;
-
同样
__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代码看起来很清楚
- 三个参数:__first和__last是传入的迭代器,__comp是默认的比较器或者是我们自定义的比较器。
- 代码逻辑:如果[first, last)区间有元素即不为空时,调用一次
std::__introsort_loop()
函数,然后再调用一次std::__final_insertion_sort()
函数。 - 返回值为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()
进行排序。
这里就涉及几个细节:
- 如何划分左半段右半段,由
__unguarded_partition_pivot
实现。 - 最后一小段无序的左半部分排序由
__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()
函数主要有两大步骤:
-
进行
__introsort_loop()
,这一步是精华所在,根据中枢值划分左右部分,对左右部分进行递归__introsort_loop
排序,当递归层数大于lg(n) * 2时进行堆排序,其中n为总序列长度;递归时,当序列长度小于_S_threshold即16时,返回。最终__introsort_loop
结束时,[fast, last)基本有序,可以看成由若干个长度小于_S_threshold的有序序列构成。 -
经过上步后,序列整体有序,再进行
__final_insertion_sort
,插入排序里面又有很多优化,具体见上述代码注解分析,这里不再赘述。
结语
每次看源码时间耗时短,写博客耗费时间长。虽然追踪解析源码的过程挺有趣的,但是到后面写了好几个小时博客的时候,效率就变得很低了,越写越慢,希望这篇文章对你有帮助!