C++编程笔记之sort

1、sort实现
stl中的sort调用了两个函数,
__introsort_loop:可以避免最坏情况的快速排序;
__final_insertion_sort:插入排序

template <class _RandomAccessIter>
inline void sort(_RandomAccessIter __first, _RandomAccessIter __last) {
  __STL_REQUIRES(_RandomAccessIter, _Mutable_RandomAccessIterator);
  __STL_REQUIRES(typename iterator_traits<_RandomAccessIter>::value_type,
                 _LessThanComparable);
  if (__first != __last) {
    __introsort_loop(__first, __last,
                     __VALUE_TYPE(__first),
                     __lg(__last - __first) * 2);
    __final_insertion_sort(__first, __last);
  }
}

为什么有两个排序搭配:

这是因为stl实现的策略是当需要排序的元素个数少于某个阈值的时候,就从快速排序转换成插入排序。

因为数据量很少的时候,插入排序效果并不差,而且关键的是不需要递归。

现在先看看快速排序的实现:

//lg是2^k <= n中的最大k值
template <class _Size>
inline _Size __lg(_Size __n) {
  _Size __k;
  for (__k = 0; __n != 1; __n >>= 1) ++__k;
  return __k;
}

template <class _RandomAccessIter, class _Tp, class _Size>
void __introsort_loop(_RandomAccessIter __first,
                      _RandomAccessIter __last, _Tp*,
                      _Size __depth_limit)
{
  while (__last - __first > __stl_threshold) {//判断只有数组个数超过__stl_threshold的时候,才会继续使用快排。
    if (__depth_limit == 0) {//前面看出depth_limit是__lg(__last - __first) * 2,为的是防止递归深度过深
      partial_sort(__first, __last, __last);//如果递归过深,采用partial_sort,也就是堆排序。
      return;
    }
    --__depth_limit;
    _RandomAccessIter __cut =
      __unguarded_partition(__first, __last,_Tp(__median(*__first,*(__first + (__last - __first)/2),*(__last - 1))));//对于数组左边使用while进行递推
    __introsort_loop(__cut, __last, (_Tp*) 0, __depth_limit);//对右边进行递归?????????
    __last = __cut;
  }
}

__unguarded_partition:根据pivot将区间分割为两个子序列,注意该函数是没有边界检查的

template <class _RandomAccessIter, class _Tp>
_RandomAccessIter __unguarded_partition(_RandomAccessIter __first, _RandomAccessIter __last,  _Tp __pivot) 
{
  while (true) {
    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;//指针向后移动一格
  }
}    

其中的pivot用的是三中值法,这可以避开最坏情况:
就是在头尾和中点的值进行比较,取中间的值作为pivot。

__median(*__first,*(__first + (__last - __first)/2),*(__last - 1)))

插入排序:

为什么要接近排完序的数组,还需要分大小进行分段插入排序呢?

template <class _RandomAccessIter>
void __final_insertion_sort(_RandomAccessIter __first, 
                            _RandomAccessIter __last) {
  if (__last - __first > __stl_threshold) {
    __insertion_sort(__first, __first + __stl_threshold);
    __unguarded_insertion_sort(__first + __stl_threshold, __last);
  }
  else
    __insertion_sort(__first, __last);
}

template <class _RandomAccessIter>
void __insertion_sort(_RandomAccessIter __first, _RandomAccessIter __last) {
  if (__first == __last) return; 
  for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)
    __linear_insert(__first, __i,__VALUE_TYPE(__first));
}

__insertion_sort最终又会调用__linear_insert
该函数会首先判断,最小值是否已经在最左边了,如果不是直接将元素整体后移一位,将新值放在最左边。

template <class _RandomAccessIter, class _Tp>
inline void __linear_insert(_RandomAccessIter __first,  _RandomAccessIter __last, _Tp*) {
  _Tp __val = *__last;
  if (__val < *__first) {
    copy_backward(__first, __last, __last + 1);
    *__first = __val;//后一个值小于第一个值,直接将该值前面的整体移后一位,然后将第一个位置为该值
  }
  else//否则执行插入排序
    __unguarded_linear_insert(__last, __val);
}

unguarded_linear_insert
这个函数输入一个迭代器,只要判断val的值比指针所指的值小,指针就往前移动一格,直至val不小于指针所指值。

插入排序在判断逆序对同时还要判断*next是否大于0,也就是边界的判断,但是__linear_insert中的第一个if判断,已经保证最小值将必然在数组的最左边了,所以就不用再判断是否越界了。

这样又可以省下比较操作。

template <class _RandomAccessIter, class _Tp>
void __unguarded_linear_insert(_RandomAccessIter __last, _Tp __val) {
  _RandomAccessIter __next = __last;
  --__next;
  while (__val < *__next) {
    *__last = *__next;
    __last = __next;
    --__next;
  }
  *__last = __val;
}

所以为什么要接近排完序的数组,还需要分大小进行分段插入排序呢?

大体上还是因为__unguarded_insertion_sort的效率比较高,但是__unguarded_insertion_sort又没有边界检查,所以先用 __insertion_sort来进行确保边界问题,整个序列的最小值就在最左边。

2、根据快排思路,自己实现

首先是medianOfThree:
选取的三个值将会按照升序排列,递增地顺序:a[left]—a[middle] ——a[right],显然,此时a[right]比a[middle]大,所以可以将pivot放置在right-1的位置,这样就可以使得a[right]此时已经在pivot的右边了。

const int medianOfThree(vector<int> &nums,int left,int right){
    int middle = (left + right)/2;
    if(a[middle] < a[left])
        swap(a[middle],a[left]);
    if(a[right] < a[left])
        swap(a[right],a[middle]);
    if(a[right] < a[middle])
        swap(a[right],a[middle]);

    swap(a[middle],a[right-1]);
    return a[right-1];
}
void qiucksort(vector<int> &nums,int left,int right){
    int i = left,j = right - 1;
    int pivot = medianOfThree(nums,left,right);
    while(true){
        while(a[++i] < pivot){}
        while(a[--j] > pivot){}
        if(i < j)
            swap(a[i],a[j]);
        else
            break;
    }
    swap(a[i],a[right-1]);//此时i,j交替,i就是右边区间第一个值,显然右边区间所有值都大于等于pivot,所以更换有利。
    quicksort(nums,left,i-1);
    quicksort(nums,i+1,right);
}

3、stl的递归与递推

(1)中源码中我曾经有些疑问,就是为什么左边是递推右边是递归呢?

其实这跟数组个数小于阈值后使用插入是一个道理。为了减少递归调用的开销,一半是递归一半是递推,__last = __cu之后,再进行一次while循环,最终会调用partition进行分割的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值