Victory_Defeat
2019-08-28 12:52:58
相信每个入门的同学都见过这样一个题目:
给定一些整数,将它们从小到大排序后输出
同学们常常会写这样的代码:
#include<bits/stdc++.h>
using namespace std;
int n,a[1000010];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<n;++i)printf("%d ",a[i]);
printf("%d\n",a[n]);
return 0;
}
这里的sort
就是我们今天的主角
别看它语句短小,但却无比精悍
这样就可以理解了,快排保证了前
所以,这样只要
以为这就结束了?想的太简单了
观察__final_insertion_sort
源码:
template<typename _RandomAccessIterator, typename _Compare>
void
__final_insertion_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
if (__last - __first > int(_S_threshold))
{
std::__insertion_sort(__first, __first + int(_S_threshold), __comp);
std::__unguarded_insertion_sort(__first + int(_S_threshold), __last,
__comp);
}
else
std::__insertion_sort(__first, __last, __comp);
}
可以看到,又调用了两个插入排序,它们分别的代码:
template<typename _RandomAccessIterator, typename _Compare>
void
__insertion_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
if (__first == __last) return;
for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i)
{
if (__comp(*__i, *__first))
{
typename iterator_traits<_RandomAccessIterator>::value_type
__val = _GLIBCXX_MOVE(*__i);
_GLIBCXX_MOVE_BACKWARD3(__first, __i, __i + 1);
*__first = _GLIBCXX_MOVE(__val);
}
else
std::__unguarded_linear_insert(__i, __comp);
}
}
template<typename _RandomAccessIterator, typename _Compare>
inline void
__unguarded_insertion_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
typedef typename iterator_traits<_RandomAccessIterator>::value_type
_ValueType;
for (_RandomAccessIterator __i = __first; __i != __last; ++__i)
std::__unguarded_linear_insert(__i, __comp);
}
此时此刻,您的内心一定是崩溃的
这里就不贴__unguarded_linear_insert
的代码了,只需要知道其作用
其作用是找到应插入的位置并插入(无底洞啊,不建议自己查看)
而如果,当前值要插在前头,直接让其他的后移
理论上和普通插排毫无区别实际也是,但是略微对常数有所优化
那__unguarded_insertion_sort
与__insertion_sort
有何区别?又有什么用?
貌似是省去了if
的判断句?
仅此而已?!
对,仅此而已。
但是为什么可以去掉呢?
因为这一排序是建立在最左边永远是最小值的基础上的
不仅是__unguarded_insertion_sort
、__unguarded_partition
,事实上,所有的以__unguarded
开头的函数
都不会考虑越界!
而众所周知,比较函数是很耗时的,因此常数会有较大提升
我们从各种操作入手分析
首先,经典插排,
其次,__insertion_sort
分两种情况:
每次第一分支,即if
语句执行情况,
每次第二分支,即else
语句执行情况,
那么假设二者出现概率相同,则平均为
可以看到,少了
而且,已知比较时间开销很大,赋值小一些,而减法、自减基本不耗时
而__unguarded_insertion_sort
则是
不过,在
(以上复杂度请自行证明)
您:……看个文章还要自证???
既然__unguarded_insertion_sort
的时间要小得多,那么为什么不直接用呢?
不知道读者有没有注意到2.2.2最后有一行加粗的字体
这一行字解释了为什么不能直接用__unguarded_insertion_sort
排序
等等,如何保证在__insertion_sort
后,全局最小值在左边呢?
先回到__introsort_loop
,它在什么情况下会返回呢?
一是区域小于等于16(即_S_threshold
),二是超过depth_limit
,也就是
而由快排定义可知,左边区间的所有数据一定比右边小(也可参考图):
所以,如果是第一种情况,就可以得出最小值在左边
如果是第二种情况,那么最左边的区间会调用堆排序,所以这段区间的最小值一定位于最左端。再加上左边区间所有的数据一定比右边小,那么最左边的数据一定是全局最小值
至此,我们完成了对sort
的初步探究(仅是初步)
那么,是不是所有容器都能使用sort
呢?
并不是,主要有vector
、list
、数组可以使用
unordered_
开头的容器只有前向迭代器,然而在1中已经说过,只有随机迭代器才能使用sort
而,map
、set
、priority_queue
这类自带排序的当然是用不了了
而queue
、stack
这类则因为它们对出口和入口做了限制,无法排序
那list
呢?它的迭代器是双向迭代器,也不行
不过不必担心,众所周知,list
自带list::sort
,虽然不能用std
,但可以自己使用
万万没想到啊,一个小小的sort
居然有这么多优化
不得不说,C++ STL
编写者真的把编译器的效率压榨了不少,真是视效率如生命啊!
试问:有多少人能够自己写出像STL
这样好的库?
这正是C++优点所在(并未引战)
什么?平板电视?反正平板电视也没sort
这倒是让我想起一个东西:平方根倒数速算法
下一个迫害来源,万♂恶♂之♂源