给定N个(长整型范围内的)整数,要求输出从小到大排序后的结果。
本题旨在测试各种不同的排序算法在各种数据情况下的表现。各组测试数据特点如下:
- 数据0:只有1个元素;
- 数据1:11个不相同的整数,测试基本正确性;
- 数据2:103个随机整数;
- 数据3:104个随机整数;
- 数据4:105个随机整数;
- 数据5:105个顺序整数;
- 数据6:105个逆序整数;
- 数据7:105个基本有序的整数;
- 数据8:105个随机正整数,每个数字不超过1000。
输入格式:
输入第一行给出正整数N(<= 105),随后一行给出N个(长整型范围内的)整数,其间以空格分隔。
输出格式:
在一行中输出从小到大排序后的结果,数字间以1个空格分隔,行末不得有多余空格。
输入样例:11 4 981 10 -17 0 -20 29 50 8 43 -5输出样例:
-20 -17 -5 0 4 8 10 29 43 50 981/*2015.7.13cyq*/ #include <iostream> #include <vector> #include <algorithm> using namespace std; //选择排序O(N2),case0~8(ms):1,1,2,39,后面100K个int的5个case超时 void selectSort(vector<int> &a,int begin,int end){ for(int i=begin;i<end;i++){ int min=i; for(int j=i+1;j<=end;j++){ if(a[j]<a[min]) min=j; } swap(a[i],a[min]); } } //冒泡排序O(N2),case0~8(ms):1,1,2,124(比选择排序差),后面100K个int的5个case超时 void bubbleSort(vector<int> &a,int begin,int end){ int n=end-begin+1; for(int i=0;i<n-1;i++) for(int j=begin;j<end-i;j++){ if(a[j]>a[j+1]) swap(a[j],a[j+1]); } } //直接插入排序,case0~8(ms):1,1,2,21,1656,38,超时,53,1653 void insertSort(vector<int> &a,int begin,int end){ int tmp; for(int i=begin+1;i<=end;i++){ tmp=a[i]; int j=i-1; while(j>=begin&&a[j]>tmp){ a[j+1]=a[j]; j--; } a[j+1]=tmp; } } //希尔排序,将序号相差increment的记录组成子序列,在子序列内部直接插入排序,让整个序列基本有序 //逐渐减小increment,最后一次增量为1,退化为对基本有序数组进行直接插入排序 //case0~8(ms):1,1,2,6,48,40,41,40,40 void shellSort(vector<int> &a,int begin,int end){ int i,j,tmp; int n=end-begin+1; int increment=n; do{ increment=increment/3+1;//increment为1时就是普通的直接插入排序 for(i=begin+increment;i<=end;i++){ tmp=a[i]; j=i-increment; while(j>=begin&&a[j]>tmp){ a[j+increment]=a[j]; j=j-increment; } a[j+increment]=tmp; } }while(increment>1); } //堆排序,建立大根堆,排序时每次都把堆顶元素换到最后,调整大根堆 //具体注释可见我的另一篇博客PAT 数据结构 04-树9. Path in a Heap (25) //case0~8(ms):1,1,1,6,47,44,44,44,40 void adjustDown(vector<int> &a,int root,int n){ a[0]=a[root]; for(int i=2*root;i<=n;i*=2){ if(i<n&&a[i]<a[i+1]) i++; if(a[0]>=a[i]) break; else{ a[root]=a[i]; root=i; } } a[root]=a[0]; } void buildMaxHeap(vector<int> &a,int n){ for(int i=n/2;i>0;i--) adjustDown(a,i,n); } void heapSort(vector<int> &a,int end){//begin固定为1,便于编码 buildMaxHeap(a,end); for(int i=end;i>1;i--){ swap(a[i],a[1]); adjustDown(a,1,i-1); } } //归并排序,每2个,每4个,每8个...进行归并,此处我用迭代写法 //case0~8(ms):1,1,2,6,52,48,46,46,44 void merge(vector<int> &a,int begin1,int end1,int begin2,int end2){//合并两个有序序列 int n=end2-begin1+1; vector<int> b(n); int i1=begin1; int i2=begin2; int i=0; while(i1<=end1&&i2<=end2){ if(a[i1]<=a[i2]) //此处=号保证排序的稳定性 b[i++]=a[i1++]; else b[i++]=a[i2++]; } if(i1<=end1) while(i<n) b[i++]=a[i1++]; if(i2<=end2) while(i<n) b[i++]=a[i2++]; i=begin1; int j=0; while(j<n) a[i++]=b[j++]; } void mergePerCount(vector<int> &a,int begin,int end,int count){//目标是每count个为一组 int i=begin; while(i<=end){ if(i+count-1<=end){//两个子数组加起来够count个 merge(a,i,i+count/2-1,i+count/2,i+count-1); }else{ if(i+count/2<=end)//存在两个子数组,后一个残缺 merge(a,i,i+count/2-1,i+count/2,end); } i=i+count; } } void mergeSort(vector<int> &a,int begin,int end){ int count=1; int n=end-begin+1; while(count<n){ count=count*2; mergePerCount(a,begin,end,count); } } //普通快速排序,piovt太小或者太大会导致性能很差 //case0~8(ms):1,1,超时,超时,超时,超时,超时,2222,超时 int partition1(vector<int> &a,int begin,int end){ int pivot=a[begin]; while(begin<end){ while(begin<end&&a[end]>pivot) end--; a[begin]=a[end]; while(begin<end&&a[begin]<pivot) begin++; a[end]=a[begin]; } a[begin]=pivot; return begin; } void quickSort1(vector<int> &a,int begin,int end){ if(begin<end){ int pivotpos=partition1(a,begin,end); quickSort1(a,begin,pivotpos-1); quickSort1(a,pivotpos+1,end); } } //快速排序和直接插入排序混合 //case0~8(ms):1,1,2,11,超时,39,39,76,超时 int partition2(vector<int> &a,int begin,int end){ //3数取中 int mid=begin+(end-begin)/2; if(a[begin]>a[end]) swap(a[begin],a[end]); if(a[mid]>a[end]) swap(a[mid],a[end]); if(a[mid]>a[begin]) swap(a[mid],a[begin]); int pivot=a[begin]; while(begin<end){ while(begin<end&&a[end]>pivot) end--; a[begin]=a[end]; while(begin<end&&a[begin]<pivot) begin++; a[end]=a[begin]; } a[begin]=pivot; return begin; } void quickSort2(vector<int> &a,int begin,int end){ if(begin<end){ if(end-begin<5000) insertSort(a,begin,end); else{ int pivotpos=partition2(a,begin,end); quickSort2(a,begin,pivotpos-1); quickSort2(a,pivotpos+1,end); } } } int main(){ int N; cin>>N; vector<int> a(N+1); for(int i=1;i<N+1;i++) //从1开始是为了便于堆排的层序编码 cin>>a[i]; //selectSort(a,1,N); //bubbleSort(a,1,N); //insertSort(a,1,N); //shellSort(a,1,N); //heapSort(a,N); //mergeSort(a,1,N); //quickSort1(a,1,N); //quickSort2(a,1,N); sort(a.begin()+1,a.end()); cout<<a[1]; for(int i=2;i<N+1;i++) cout<<" "<<a[i]; return 0; }
测试结果:
选择排序:O(n2);
冒泡排序:O(n2),顺序最好O(n),逆序最差n2/2,考虑到交换次数多,略差于选择排序;
直接插入排序:O(n2),顺序最好O(n),逆序最差n2/2,平均n2/4,好于选择和冒泡,在简单排序中性能最好;
希尔排序:O(n3/2),相当于先粗调,再精调的直接插入排序。
堆排序:O(nlogn),初始建堆次数较多,不适合排序个数较少的情况。
归并排序:O(nlogn),使用迭代写法,空间复杂度为O(n)。
快速排序:O(nlogn),最差O(n2),这是因为最好情况递归树深度为logn,最坏情况递归树深度为n。pivot的选取非常重要,太大或者太小会导致性能恶化。
可在选取pivot,序列较短时使用直接插入排序,迭代代替递归等方面进行优化。
STL sort:快排、直插、堆排(防止递归树太深)混合。
改进算法中,对于100K以内的数据,希尔排序、堆排序、归并排序与STL sort的性能较接近,快速排序需要优化才能发挥出它的力量。