STL源码解释-----sort()&&partial_sort()

本文详细解析了STL中的sort函数实现,包括其快速排序和插入排序的混合使用策略,以及部分排序和堆排序的应用场景。并通过源码解读了如何在不同数据规模下选择最优排序算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

直接贴第一段代码:

template<typename _RandomAccessIterator>
inline void
sort(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
typedef typename iterator_traits<_RandomAccessIterator>::value_type
 _ValueType;
// concept requirements
__glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
  _RandomAccessIterator>)
__glibcxx_function_requires(_LessThanComparableConcept<_ValueType>)
__glibcxx_requires_valid_range(__first, __last);
if (__first != __last)
 {
  std::__introsort_loop(__first, __last,std::__lg(__last – __first) * 2);
  std::__final_insertion_sort(__first, __last);
 }
}

:sort函数的前后范围区间定义的是两个迭代器,可以随机访问,但是不可以访问链表。这里首先讲一下没有自定义cmp函数的版本。排序的条件是first != last,也就是非空。

STL的排序是分为两个部分的,一个是优化的快排,另外一个是插入排序。可以理解,当排序数目少到一定数目,是插排的效率比较高的。这里采用(也是gcc采用的版本)都是选择前中后三个元素的中值来作为key值的,而且这里的优化快排函数还有第三个参数是lg(last - first)*2,主要是用来限制递归次数的,因为太深的递归需要很大的时间和空间开销,所以这里就限制快排递归到只剩一两个元素。

这里看看优化快排代码:

template<typename _RandomAccessIterator, typename _Size>
void
__introsort_loop(_RandomAccessIterator __first,
  _RandomAccessIterator __last,
  _Size __depth_limit)
{
typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;
while (__last – __first > int(_S_threshold))
 {
  if (__depth_limit == 0)
  {
  _GLIBCXX_STD_P::partial_sort(__first, __last, __last);
  return;
  }
  –__depth_limit;
  _RandomAccessIterator __cut =
  std::__unguarded_partition(__first, __last, _ValueType(std::__median(*__first,*(__first+ (__last- __first)/ 2),*(__last – 1))));
  std::__introsort_loop(__cut, __last, __depth_limit);
  __last = __cut;
 }
}
发现刚才所说的那个递归次数被定义为__depth_limit,当递归太深(==0)且区间大小大于16的时候就不递归了,这个时候它就调用一个叫partial_sort的函数,这个也是stl库的一个内置函数,实现的功能就是部分排序,这里刚好可以用上。既然都看到这里了,我们不妨就把这个部分排序函数的源码也看看了。

部分排序:

template<typename _RandomAccessIterator>
inline void
partial_sort(_RandomAccessIterator __first,
  _RandomAccessIterator __middle,
  _RandomAccessIterator __last)
{
typedef typename iterator_traits<_RandomAccessIterator>::value_type
 _ValueType;
// concept requirements
__glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
  _RandomAccessIterator>)
__glibcxx_function_requires(_LessThanComparableConcept<_ValueType>)
__glibcxx_requires_valid_range(__first, __middle);
__glibcxx_requires_valid_range(__middle, __last);
std::__heap_select(__first, __middle, __last);
std::sort_heap(__first, __middle);
}
这里采用的是堆排序,实现方式我基本看了一下,其实就是让前半部分形成一个最大堆,然后用for跟后面的一个一个比较,如果比堆根部最大的要小就交换两者位置(也就是扔掉大的),然后保持最大堆,然后扫描一次之后堆元素肯定比外面的小了,然后直接按照堆排序的算法,就能排出来了。

但是这里我觉得很诡异,因为他写的局部排序的三个参数居然是,first,last,last,也就说中间的定位参数居然也是last,也就是说这个根本就是全排,为什么要用局排呢,难道就是为了里面的堆排序么,如果是这样为什么不直接调用堆排呢?

这里的插入排序起的名字叫做__final_insertion_sort大致可以猜到,肯定是最后才用的插入排序,因为原队列越是整齐,插排的效率越高(希尔排序用的就是这个原理)。

最后的插排:

template<typename _RandomAccessIterator>
void
__final_insertion_sort(_RandomAccessIterator __first,
  _RandomAccessIterator __last)
{
if (__last – __first > int(_S_threshold))
 {
  std::__insertion_sort(__first, __first + int(_S_threshold));
  std::__unguarded_insertion_sort(__first + int(_S_threshold), __last);
 }
else
 std::__insertion_sort(__first, __last);
}
之前的优化快排有个定义是在enum { _S_threshold = 16 },这个16就是区间幅度,当区间小于16时就用插排。而且这里其实是有两个插排的,一个是unguarded的,一个是final的如果最后的数小于等于16个,直接就final了,如果超过十六个,那就前十六个final后面的unguarded。两者的区别就是final是正规插排,而unguarded就是把数插到前面的已经有序的区间内











原来虽然Quick和Heap的时间复杂性是一样的,但堆排序的常熟因子还是大些的,并且堆排序过程中重组堆其实也不是个省时的事。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值