快速排序被认为是所有同数量级排序(O(n*logn))的排序方法中平均性能最好的,同时它也是C++sort,C语言qsort函数的底层实现(下面简述快排)。
个人理解快排是分治思想的排序,每次找一个中心点,将比其小的丢到它左边,比其大的丢到它右边(以排成递增序列为例),下次就对其左边和右边的两个子区间进行这样的操作。
思路:
每次选出一个中心点(最传统的选法是直接选区间第一个元素)先存到一个辅助空间上,再设两个指针,一个指向区间左端,一个指向区间右端:
对于右边的指针: 若指向的值比中心点小,就把它丢到左边上一个指向位置的空间中去(第一步的中心点就是从左指针位置选出,这样可以保证上一次位置的元素已成功已走,即该位置为空出状态);
对于左边的指针: 同理,若指向的值比中心点大,则丢到右边上一次指向的位置中去;
这样,快速排序的基本代码就可以写出来了。
以下以洛谷P1177为例分析传统快排及其改进。
代码:
1.传统快排:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100050
int my_array[maxn];
void quick_sort(int* my_array,int l,int r)
{
if(l<r)
{
int low=l;
int high=r;
int num=my_array[low];
while(low<high)
{
while(low<high && my_array[high]>=num) high--;
my_array[low]=my_array[high];
while(low<high && my_array[low]<=num) low++;
my_array[high]=my_array[low];
}
//当low==high时,循环结束
my_array[low]=num;
quick_sort(my_array,l,low-1);
quick_sort(my_array,high+1,r);
}
}
int main()
{
int i,j;
int n,m;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&my_array[i]);
}
quick_sort(my_array,1,n);
for(i=1;i<=n;i++)
{
if(i!=n)
{
printf("%d ",my_array[i]);
}
else
{
printf("%d\n",my_array[i]);
}
}
return 0;
}
这样的快排由于每次中心点都是选取区间第一个数,则若初始序列已经基本有序了,则其便退化为起泡排序O(n^2)了。
以洛谷P1177为例:传统快排只能过前两个点,后面的三个点都被卡了时间。
改进的方法有"三者取中",
即每次比较array[low], array[(low+high)/2], array[high],取三者的关键值中值作为中心点,这样可大大改善最坏情况的性能。
将上面的传统快排中心点选取swap一下就好了。
代码:
2."三者取中"快排
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100050
int my_array[maxn];
//取中值位置
int find_mid(int* my_array,int pos1,int pos2,int pos3)
{
int midpos;
if((my_array[pos1]>=my_array[pos2] && my_array[pos1]<=my_array[pos3])||(my_array[pos1]<=my_array[pos2] && my_array[pos1]>=my_array[pos3]))
{
midpos=pos1;
}
else if((my_array[pos2]>=my_array[pos1] && my_array[pos2]<=my_array[pos3])||(my_array[pos2]<=my_array[pos1] && my_array[pos2]>=my_array[pos3]))
{
midpos=pos2;
}
else
{
midpos=pos3;
}
return midpos;
}
void quick_sort(int* my_array,int l,int r)
{
if(l<r)
{
int low=l;
int high=r;
int mid=find_mid(my_array,low,(low+high)/2,high);
swap(my_array[low],my_array[mid]);
int num=my_array[low];
while(low<high)
{
while(low<high && my_array[high]>=num) high--;
my_array[low]=my_array[high];
while(low<high && my_array[low]<=num) low++;
my_array[high]=my_array[low];
}
//当low==high时,循环结束
my_array[low]=num;
quick_sort(my_array,l,low-1);
quick_sort(my_array,high+1,r);
}
}
int main()
{
int i,j;
int n,m;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&my_array[i]);
}
quick_sort(my_array,1,n);
for(i=1;i<=n;i++)
{
if(i!=n)
{
printf("%d ",my_array[i]);
}
else
{
printf("%d\n",my_array[i]);
}
}
return 0;
}
这样改进后快排就效率高些了,过了4个点,最后一个点还是TLE了…
然后学习到还有一种思路是选择随机数位置,即每次中心点的选择采用随机数确定。
代码如下:
3."随机数确定位置"快排
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100005
int my_array[maxn];
void quick_sort(int* my_array,int l,int r)
{
if(l<r)
{
int low=l;
int high=r;
int rand_pos=rand()%(r-l+1)+l;//随机数确定位置;
swap(my_array[low],my_array[rand_pos]);
int num=my_array[low];
while(low<high)
{
while(low<high && my_array[high]>=num) high--;
my_array[low]=my_array[high];
while(low<high && my_array[low]<=num) low++;
my_array[high]=my_array[low];
}
//当low==high时,循环结束
my_array[low]=num;
quick_sort(my_array,l,low-1);
quick_sort(my_array,high+1,r);
}
}
int main()
{
int i,j;
int n,m;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&my_array[i]);
}
quick_sort(my_array,1,n);
for(i=1;i<=n;i++)
{
if(i!=n)
{
printf("%d ",my_array[i]);
}
else
{
printf("%d\n",my_array[i]);
}
}
return 0;
}
但是即使这样它还是过不了最后一个测试点,但是相比于三者取中测试的用时又少了一点(当然,因为是随机数,下次测也有可能多…)
原来最后一个测试点是全部元素值相同的数据序列,那这样的话所有的区间划分和递归都是无意义的…
无奈之下,函数swap改成手写swap,不用scanf输入数据,用手写版快读模板读入数据,加上O2优化,终于卡过了…
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100005
int my_array[maxn];
inline int read()//此方法只能读入整数;
{
int sgn = 1; int cnt = 0; //sgn表示正负号,cnt表示读取的数字
char ch = getchar();
while (ch < '0' || ch > '9')//读取非数字的字符,保留负号,忽略其余无关符号
{
if(ch == '-')
sgn = -sgn;
ch = getchar();
}
while ('0' <= ch && ch <= '9')
{
cnt = cnt*10 + (ch-'0');
ch = getchar();
}
return sgn*cnt;
}
int find_mid(int* my_array,int pos1,int pos2,int pos3)
{
int midpos;
if((my_array[pos1]>=my_array[pos2] && my_array[pos1]<=my_array[pos3])||(my_array[pos1]<=my_array[pos2] && my_array[pos1]>=my_array[pos3]))
{
midpos=pos1;
}
else if((my_array[pos2]>=my_array[pos1] && my_array[pos2]<=my_array[pos3])||(my_array[pos2]<=my_array[pos1] && my_array[pos2]>=my_array[pos3]))
{
midpos=pos2;
}
else
{
midpos=pos3;
}
return midpos;
}
void quick_sort(int* my_array,int l,int r)
{
if(l<r)
{
int low=l;
int high=r;
int mid=find_mid(my_array,low,(low+high)/2,high);
int t;
//swap:
if(mid!=low)
{
t=my_array[low];
my_array[low]=my_array[mid];
my_array[mid]=t;
}
int num=my_array[low];
while(low<high)
{
while(low<high && my_array[high]>=num) high--;
my_array[low]=my_array[high];
while(low<high && my_array[low]<=num) low++;
my_array[high]=my_array[low];
}
//当low==high时,循环结束
my_array[low]=num;
quick_sort(my_array,l,low-1);
quick_sort(my_array,high+1,r);
}
}
int main()
{
int i,j;
int n,m;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
my_array[i]=read();
}
quick_sort(my_array,1,n);
for(i=1;i<=n;i++)
{
if(i!=n)
{
printf("%d ",my_array[i]);
}
else
{
printf("%d\n",my_array[i]);
}
}
return 0;
}
所以说,C++sort,C语言qsort快排底层肯定实现没那么简单(上几百行代码…)
贴一小部分C中qsort和C++中sort实现代码(C语言用了指针做,C++用了迭代器做),有兴趣的大佬可以去编译器自行查看。
//C语言qsort:仅函数名部分
void __cdecl qsort(void *_Base,size_t _NumOfElements,size_t _SizeOfElements,int (__cdecl *_PtFuncCompare)(const void *,const void *));
#endif
//C++sort:
template<typename _RandomAccessIterator, typename _Compare>
inline void
sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp)
{
// concept requirements
__glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
_RandomAccessIterator>)
__glibcxx_function_requires(_BinaryPredicateConcept<_Compare,
typename iterator_traits<_RandomAccessIterator>::value_type,
typename iterator_traits<_RandomAccessIterator>::value_type>)
__glibcxx_requires_valid_range(__first, __last);
std::__sort(__first, __last, __gnu_cxx::__ops::__iter_comp_iter(__comp));
}