之前一直使用STL中的sort来完成O(nlogn)的排序。忽然发觉还不会快速排序,经过一番学习有几点心得。
市面上常见到的排序算法一共有九种,按类可划分为三大类:
1.选择排序,冒泡排序,插入排序
2.快速排序,归并排序,堆排序
3.计数排序,基数排序,桶排序
前两类是基于比较的排序算法,通俗来说就是就是通过比较元素的大小进行排序。前者为O(n*n),后者O(nlongn)。但这只是粗略的估算,因为前者在一些数据下也可以是O(nlogn),而后者如果基准元素找的不好也可能退化为O(n*n)(以快速排序为例后面着重说)。第三类则是使用按按位划分,位映射的思想来排序,此时时间复杂度不仅和n的大小有关,还与元素的值的大小有关,局限性较大。但比如桶的思想在除排序之外的其他地方也有应用。由于第一类较慢,第三类局限性大,下面着重讲解第二类中最具代表性的快速排序。
快速排序的思想基于分治,顾名思义是分而治之。我们现在一个序列(标号是l~r)中随机找一个基准元素,比如可以找序列中的第一个数a[l]记为x,然后扫描这个序列,遇到比x小的放入数组b中,和x一样大的放入数组c中,比x大的放入数组d中。此时将bcd数组按顺序放入a数组中,前bb个数均比x小,后dd个数均比x大,我们只需再排这两个序列即可,方法和上面一样。下面有一个魔鬼细节,如果序列为1 2 3 4 5........100000,那么会超时。因为我们每次基准元素找的都是第一个,那么b序列没有数,c序列只有一个数,d序列有n-1个,那么需要循环n-1次,每次平均扫描n>>1个数,时间复杂度退化到n*n。可见面对一些极为特殊的数据无法通过,而一些比赛就喜欢出这种极端的数据,因此我们可以在l~r中随机取数避免这种情况。
#include <bits/stdc++.h>
using namespace std;
const int N=100001;
int n,a[N],b[N],c[N],d[N];
void qsort(int l,int r)
{
if(l>=r) return ;
int x=a[(l+r)>>1],bb=0,cc=0,dd=0;
for(int i=l;i<=r;i++)
{
if(a[i]<x) b[++bb]=a[i];
if(a[i]==x) c[++cc]=a[i];
if(a[i]>x) d[++dd]=a[i];
}
for(int i=1;i<=bb;i++)
a[i+l-1]=b[i];
for(int i=1;i<=cc;i++)
a[i+l+bb-1]=c[i];
for(int i=1;i<=dd;i++)
a[i+l+bb+cc-1]=d[i];
qsort(l,l+bb-1);
qsort(l+bb+cc,r);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
qsort(1,n);
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
}