首先C++不是必须需要学会排序这个东西的,但是有时候如果要一边排序、一边干点东西,或者有什么严苛的要求,那就得会手写排序
这属于模板性的东西
桶排序
时间复杂度: O ( n )
优点:速度最快
缺点:占用空间最大(和值域有关)、难以一边排序一边保留其他信息、数组不能用小数或者分数为编号
太容易了,不写代码了
冒泡排序
时间复杂度:O(n)
优点:手写快
缺点:太慢,不稳定
#include<iostream>
using namespace std;
int a[100001];
int main()
{
int n,num;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n-i;j++)
{
if(a[j]>a[j+1])
{
num=a[j];
a[j]=a[j+1];
a[j+1]=num;
}
}
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
return 0;
}
“剪枝”后:
#include<iostream>
using namespace std;
int a[100001],n;
bool check1()
{
bool flag=1;
for(int i=2;i<=n;i++)
if(a[i-1]>a[i]) flag=0;
if(flag) return true;
else return false;
}
bool check2()
{
bool flag=1;
for(int i=2;i<=n;i++)
if(a[i-1]<a[i]) flag=0;
if(flag) return true;
else return false;
}
int main()
{
int num,cnt_1=0,cnt_2=0;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=2;i<=n;i++)
{
if(a[i]>a[i-1]) cnt_1++;
if(a[i]<a[i-1]) cnt_2++;
}
if(cnt_1>=cnt_2)
{
for(int i=1;i<=n;i++)
{
if(check1()) break;
for(int j=1;j<=n-i;j++)
{
if(a[j]>a[j+1])
{
num=a[j];
a[j]=a[j+1];
a[j+1]=num;
}
}
}
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
}
else{
for(int i=1;i<=n;i++)
{
if(check2()) break;
for(int j=i;j<n;j++)
{
if(a[j]<a[j+1])
{
num=a[j];
a[j]=a[j+1];
a[j+1]=num;
}
}
}
for(int i=n;i>=1;i--)
cout<<a[i]<<" ";
}
return 0;
}
但是仍然不够快,只能说还是骗分的排序算法,只是多骗点分罢了
归并排序
也是手写的情况下我最喜欢的一种排序
时间复杂度:O ( n log n )
优缺点:非常折中的一种算法
#include<iostream>
using namespace std;
int n,a[100001],medium[100001],num;
void solve(int left,int right)
{
if(left==right) return;
if((left+1)==right) {
if(a[left]>a[right])
{
num=a[left];
a[left]=a[right];
a[right]=num;
}
}
else
{
solve(left,(left+right)/2);
solve((left+right)/2+1,right);
int leftpoint=left;
int rightpoint=(left+right)/2+1;
int cnt=leftpoint;
while(1)
{
if(leftpoint==((left+right)/2+1))
{
for(int i=rightpoint;i<=right;i++)
{
medium[cnt]=a[i];
cnt++;
}
break;
}
else if(rightpoint==(right+1))
{
for(int i=leftpoint;i<=(left+right)/2;i++)
{
medium[cnt]=a[i];
cnt++;
}
break;
}
if(a[leftpoint]<=a[rightpoint]){
medium[cnt]=a[leftpoint];
leftpoint++;
}
else{
medium[cnt]=a[rightpoint];
rightpoint++;
}
cnt++;
}
for(int i=left;i<=right;i++)
a[i]=medium[i];
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
solve(1,n);
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
return 0;
}
如何分析算法时间复杂度?可以大概想一想这个图,样子就是个二叉树
每次合并时间复杂度O(n)(忽略常数),一共合并n次(原理就是任意一个正整数都可以被拆成2 的幂之和),那么时间复杂度是O(n)吗?
不是,因为每次合并时间复杂度其实是O(n/2),除去的(也就是忽略的)并不是一个不依赖于n的常数
那么对于每一层考虑,分别是n,n/2+n/2,n/4+n/4+n/4+n/4,...
其实是n+n+n+...
一共有多少个n?是二叉树的深度log n
所以时间复杂度 O ( n log n )
快速排序
时间复杂度:最快近似 O ( n ) ,平均 O ( n log n ),最慢 O ( n )退化成冒泡排序
优点:有时候很快
缺点:不稳定
我最开始写的不太行,速度太慢还是TLE了,这个代码如下:
#include<iostream>
using namespace std;
int n,a[100001],num;
void solve(int l,int r)
{
if(l>=r) return;
int lpoint=l,rpoint=r;
while(1)
{
if(lpoint==rpoint)
{
num=a[lpoint];
a[lpoint]=a[l];
a[l]=num;
solve(l,lpoint-1);
solve(rpoint+1,r);
break;
}
else if(a[rpoint]>=a[l]) rpoint--;
else if(a[lpoint]<=a[l]) lpoint++;
if((a[rpoint]<a[l])&&(a[lpoint]>a[l]))
{
num=a[rpoint];
a[rpoint]=a[lpoint];
a[lpoint]=num;
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
solve(1,n);
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
return 0;
}
多开几个数组存下不同序列,再合并,改进之后:
#include<iostream>
using namespace std;
int n,a[100001],num;
int b[100001],c[100001],d[100001];
void solve(int l,int r)
{
if(l>=r) return;
int cnt_b=0,cnt_c=0,cnt_d=0;
c[cnt_c++]=a[l];
for(int i=l+1;i<=r;i++)
{
if(a[i]<a[l]) b[cnt_b++]=a[i];
else if(a[i]==a[l]) c[cnt_c++]=a[i];
else d[cnt_d++]=a[i];
}
int cnt=l;
int ll=l+cnt_b-1;
int rr=r-cnt_d+1;
while(cnt_b--) a[cnt++]=b[cnt_b];
while(cnt_c--) a[cnt++]=c[cnt_c];
while(cnt_d--) a[cnt++]=d[cnt_d];
solve(l,ll);
solve(rr,r);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
solve(1,n);
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
return 0;
}
这样还是会TLE的,因为没有随机化,随机化挺麻烦的,所以一般手写用归并排序(又稳定又好写)
竞赛可以用随机数,但是注意不要用时间当种子(因为那样直接就零分了)