C++编程之stable_sort

一、sort()的潜在隐患

sort()最坏情况的改进


快速排序有着O(NlgN)的平均时间复杂度,如果遇到极端的情况可能会恶化到N^2。
但是在STL中,已经针对快速排序可能出现:
(1)pivot选取不当;
(2)递归深度过深;
两个问题分别给出了:
(1)三中值法:保证pivot选取合适;
(2)递归深度判断,过深直接堆排序。
将快速排序基本保持在了O(NlgN),有效地避免了最坏情况地发生。


但是仔细分析源码,下面是分割的具体实现:

 while (*__first < __pivot)//当左值小于pivot,f向后移动,当左值大于等于pivot,停止
      ++__first;
    --__last;
    while (__pivot < *__last)////当右值大于pivot,l向前移动,当右值小于等于pivot,停止
      --__last;
    if (!(__first < __last))//如果两个指针接错,循环停止
      return __first;
    iter_swap(__first, __last);//否则交换
    ++__first;//指针向后移动一格

两个while分别用<和>进行了判断,

注意》》》》》如果此时first和last的值都等于pivot,那么两者仍然需要进行交换,当然两个相等的值交换并不影响什么,但是如果值还附带不同的属性呢,然后属性和值一同打包为一个对象,显然我们还是希望每次排序都是一样的。

这就是快速排序的不稳定的问题,为此STL中还有稳定版的sort。


二、stable_sort

快速排序不稳定,如果想要稳定的排序就换一种排序方式。

在STL中的其实就是归并排序,但是归并排序由于需要额外的数组来执行归并,所以如果没有足够的空间是不能正常归并的,STL是这样实现的:

template <class _RandomAccessIter, class _Tp, class _Distance, class _Compare>
inline void __stable_sort_aux(_RandomAccessIter __first, _RandomAccessIter __last, _Tp*, _Distance*,_Compare __comp) {
  _Temporary_buffer<_RandomAccessIter, _Tp> buf(__first, __last);//开辟一个缓冲数组
  if (buf.begin() == 0)//开辟失败了
    __inplace_stable_sort(__first, __last, __comp);//调用内部归并
  else 
    __stable_sort_adaptive(__first, __last, buf.begin(),_Distance(buf.size()), __comp);//有足够的空间,执行归并。时间复杂度在O(NlgN)
}

1、先看正常的有足够空间的归并吧:

首先,考虑的是数组我是申请了,但是万一不够大呢,于是就递归啊,直到大小合适了。

template <class _RandomAccessIter, class _Pointer, class _Distance, 
          class _Compare>
void __stable_sort_adaptive(_RandomAccessIter __first, _RandomAccessIter __last, _Pointer __buffer, _Distance __buffer_size, _Compare __comp) {
  _Distance __len = (__last - __first + 1) / 2;
  _RandomAccessIter __middle = __first + __len;
  if (__len > __buffer_size) {//判断缓冲的数组是否足够使用
    __stable_sort_adaptive(__first, __middle, __buffer, __buffer_size, __comp);//递归,将数组变小
    __stable_sort_adaptive(__middle, __last, __buffer, __buffer_size,  __comp);
  }
  else {
        __merge_sort_with_buffer(__first, __middle, __buffer, (_Distance*)0, __comp);//递归划分左边
    __merge_sort_with_buffer(__middle, __last, __buffer, (_Distance*)0,__comp);//划分右边
  }
  __merge_adaptive(__first, __middle, __last, _Distance(__middle - __first), _Distance(__last - __middle), __buffer, __buffer_size,__comp);//合并左右两边
}

现在,终于向CPU申请了足够大的内存空间,开始进入merge正题:

 __merge_sort_with_buffer

和一般的归并递归实现不一样,这里在底层用了插入排序。

template <class _RandomAccessIter, class _Pointer, class _Distance,class _Compare>

void __merge_sort_with_buffer(_RandomAccessIter __first, _RandomAccessIter __last, _Pointer __buffer,_Distance*, _Compare __comp) {
  _Distance __len = __last - __first;
  _Pointer __buffer_last = __buffer + __len;

  _Distance __step_size = __stl_chunk_size;
  __chunk_insertion_sort(__first, __last, __step_size, __comp);//先调用chunk_insertion_sort对每个长度为_S_chunk_size的子区间进行插入排序。

  while (__step_size < __len) {
    __merge_sort_loop(__first, __last, __buffer, __step_size, __comp);//
    __step_size *= 2;
    __merge_sort_loop(__buffer, __buffer_last, __first, __step_size, __comp);
    __step_size *= 2;
  }
}

先调用chunk_insertion_sort对每个长度为_S_chunk_size的子区间进行插入排序。

 __chunk_insertion_sort(__first, __last, __chunk_size)
{
  while (__last - __first >= __chunk_size) {
    __insertion_sort(__first, __first + __chunk_size);
    __first += __chunk_size;
  }
  __insertion_sort(__first, __last);
} 

合并已经插入排序好的分片

template <class _RandomAccessIter1, class _RandomAccessIter2,class _Distance, class _Compare>
void __merge_sort_loop(_RandomAccessIter1 __first,
                       _RandomAccessIter1 __last, 
                       _RandomAccessIter2 __result, _Distance __step_size,_Compare __comp) {
  _Distance __two_step = 2 * __step_size;

  while (__last - __first >= __two_step) {
    __result = merge(__first, __first + __step_size,__first + __step_size, __first + __two_step,__result,__comp);
    __first += __two_step;
  }
  __step_size = min(_Distance(__last - __first), __step_size);

  merge(__first, __first + __step_size,__first + __step_size, __last,__result,__comp);
}

其中的merge就比较正常了:

template <class _InputIter1, class _InputIter2, class _OutputIter>
_OutputIter merge(_InputIter1 __first1, _InputIter1 __last1,_InputIter2 __first2, _InputIter2 __last2,
_OutputIter __result) {
  __STL_REQUIRES(_InputIter1, _InputIterator);
  __STL_REQUIRES(_InputIter2, _InputIterator);
  __STL_REQUIRES(_OutputIter, _OutputIterator);
  __STL_REQUIRES_SAME_TYPE(
          typename iterator_traits<_InputIter1>::value_type,
          typename iterator_traits<_InputIter2>::value_type);
  __STL_REQUIRES(typename iterator_traits<_InputIter1>::value_type,
                 _LessThanComparable);
  while (__first1 != __last1 && __first2 != __last2) {
    if (*__first2 < *__first1) {
      *__result = *__first2;
      ++__first2;
    }
    else {
      *__result = *__first1;
      ++__first1;
    }
    ++__result;
  }
  return copy(__first2, __last2, copy(__first1, __last1, __result));
}

现在可以先总结一下:

首先如果数组nums的长度为len,首尾指针为left,right;

(1)首先,先判断是否有足够的缓冲数组产生;

假设有足够内存空间,那么就将nums从中间分块,划分为左右两块,每块上len/2,然后进入__merge_sort_with_buffer;

(2)对每一块以step_size又进行划分,并且调用插入排序,对每一分区进行排序;

len/2 = step_size+step_size+…….+ residual;

此时该原本长度为len/2的数组被划分为若干step_size大小以及余数的有序分区。

(3)先是同样都是step_size大小的有序数组进行归并,然后再与可能的余数部分进一步归并。最开始分得左右两边都已经是有序数组了。

开始合并最后的有序数组:

template <class _BidirectionalIter, class _Distance, class _Pointer>
void __merge_adaptive(_BidirectionalIter __first,
                      _BidirectionalIter __middle, 
                      _BidirectionalIter __last,
                      _Distance __len1, _Distance __len2,
                      _Pointer __buffer, _Distance __buffer_size) {
  if (__len1 <= __len2 && __len1 <= __buffer_size) {//如果缓冲数组足够大
    _Pointer __buffer_end = copy(__first, __middle, __buffer);//将前半部分复制到缓冲数组上
    merge(__buffer, __buffer_end, __middle, __last, __first);//合并到first
  }
  else if (__len2 <= __buffer_size) {
    _Pointer __buffer_end = copy(__middle, __last, __buffer);
    __merge_backward(__first, __middle, __buffer, __buffer_end, __last);//__merge_backward与std::merge类似
  }
  else {//如果缓冲数组不够大怎么办
    _BidirectionalIter __first_cut = __first;
    _BidirectionalIter __second_cut = __middle;
    _Distance __len11 = 0;
    _Distance __len22 = 0;
    if (__len1 > __len2) {
      __len11 = __len1 / 2;
      advance(__first_cut, __len11);
      __second_cut = lower_bound(__middle, __last, *__first_cut);
      distance(__middle, __second_cut, __len22); 
    }
    else {
      __len22 = __len2 / 2;
      advance(__second_cut, __len22);
      __first_cut = upper_bound(__first, __middle, *__second_cut);
      distance(__first, __first_cut, __len11);
    }
    _BidirectionalIter __new_middle =
      __rotate_adaptive(__first_cut, __middle, __second_cut, __len1 - __len11, __len22, __buffer, __buffer_size);
    __merge_adaptive(__first, __first_cut, __new_middle, __len11,__len22, __buffer, __buffer_size);//递归处理
    __merge_adaptive(__new_middle, __second_cut, __last, __len1 - __len11,__len2 - __len22, __buffer, __buffer_size);
  }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值