C++编程笔记之sort
来源:互联网 发布:苏轼 文言文徙知 编辑:程序博客网 时间:2024/06/07 09:36
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进行分割的。
- C++编程笔记之sort
- shell编程之sort
- Perl学习笔记之Sort
- js学习笔记之sort
- Linux+C学习笔记之IO编程
- Linux+C学习笔记之网络编程
- 《c语言编程之道》笔记
- 《c语言编程之道》笔记
- C指针编程之道 ---第一次笔记
- C指针编程之道 ---第二次笔记
- C指针编程之道 ---第三次笔记
- C指针编程之道--------笔记
- C语言学习笔记之C语言编程
- 编程备忘录之qsort和sort
- LeetCode 之 Sort Colors — C 实现
- LeetCode 之 Sort List — C 实现
- C语言快速排序之sort排序
- QT学习笔记之四 Sort
- asdasdas
- 算法课第13周第1题——486. Predict the Winner
- matlab中的分类器使用小结(SVM、KNN、RF、AdaBoost、Naive Bayes、DAC)
- [usaco]Palindromic Squares题解
- XPath 获取子节点的某个属性
- C++编程笔记之sort
- 常用Linux20条命令
- 25款最佳响应式HTML5前端开发框架
- PHP环境搭建(0)----新建虚拟机
- 足球锦标赛 华东师范
- Oracle执行计划
- 利用动态规划(非递归)探索一个高效的(n,m)组合算法,名字待定
- Fabric0.6/1.0镜像文件拉取
- HashMap实现原理