例题链接:P1177 【模板】排序
1.选择排序
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
for (int i=1;i<n;i++)
{
int k=i;
for (int j=i+1;j<=n;j++)
{
if (a[j]<a[k])
{
k=j;
}
}
swap(a[k],a[i]);
}
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
可见效率极其低下
2.冒泡排序
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
for (int i=n;i>1;i--)
{
for (int j=1;j<i;j++)
{
if (a[j]>a[j+1])
{
swap(a[j],a[j+1]);
}
}
}
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
同样效率低下,但是可以优化,若数列有序则退出
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
for (int i=n;i>1;i--)
{
int flag=1;
for (int j=1;j<i;j++)
{
if (a[j]>a[j+1])
{
swap(a[j],a[j+1]);
flag=0;
}
}
if (flag)
{
break;
}
}
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
性能优化不少
3.插入排序
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
int i,j,tem;
for (i=1;i<=n;i++)
{
tem=a[i];
for (j=i-1;j>=1;j--)
{
if (tem<a[j])
{
a[j+1]=a[j];
}
else
{
break;
}
}
a[j+1]=tem;
}
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
相对高效
4.桶排序
#include<bits/stdc++.h>
using namespace std;
int n,x,a[10000009];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>x;
a[x]++;
}
for (int i=1;i<=10000000;i++)
{
while(a[i])
{
cout<<i<<' ';
a[i]--;
}
}
return 0;
}
桶排序编码简单,速度快,但是只适用于小范围的数据,无法应用于大范围的数据排序,但是可以记录各类数据出现的次数
5.快速排序
思路:每次以区间中点数 mid 为标准,左右各用一根指针,在左区间找第一个 >=mid 的数,右区间找第一个 <=mid 的数,将他两交换,如此重复直至左指针跑到右指针的右边,这样保证了左指针左边的数均 <=mid ,右指针右边的数均 >=mid。接着递归两个区间,重复上述操作。
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void qsort(int l,int r)
{
int i=l,j=r,mid=a[(l+r)/2];
do
{
while(a[i]<mid) i++;
while(a[j]>mid) j--;
if (i<=j)
{
swap(a[i],a[j]);
i++;
j--;
}
} while (i<=j);
if (l<j) qsort(l,j);
if (i<r) qsort(i,r);
}
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
qsort(1,n);
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
快速排序实至名归,速度快,但是写法有几个细节
先来看看 ''='' 的细节
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void qsort(int l,int r)
{
int i=l,j=r,mid=a[(l+r)/2];
do
{
while(a[i]<mid) i++;
while(a[j]>mid) j--;
if (i<j)//这里等号没了
{
swap(a[i],a[j]);
i++;
j--;
}
} while (i<j);//注意这里等号没了
if (l<j) qsort(l,j);
if (i<r) qsort(i,r);
}
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
qsort(1,n);
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
将 if 和外层 while 处的 = 删除之后结果如下
竟然超时了,why?
不妨试试这组数据
2
0 1
看看vscode的报错
这说明在递归区间 ( i , r ) 时进入了死循环,这是因为只有 0 1 时,条件 i < r 是一直满足的,所以进入了死循环
再看看下一个细节
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void qsort(int l,int r)
{
int i=l,j=r,mid=a[(l+r)/2];
do
{
while(a[i]<=mid) i++;//注意这里多写了 =
while(a[j]>=mid) j--;//pay attention
if (i<=j)
{
swap(a[i],a[j]);
i++;
j--;
}
} while (i<=j);
if (l<j) qsort(l,j);
if (i<r) qsort(i,r);
}
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
qsort(1,n);
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
这种写法会导致什么后果?
这种写法下, i 会去寻找第一个大于 mid 的数,j 会寻找第一个小于 mid 的数
试想一下假如你开一个数组 int a[4] ,存入 5 5 5 5 ,则 mid = 5 ,那么,i 会一直找到 4,j 会找到 -1
,但是试图访问 a[4] 或 a[-1] 都是非法的。
6.归并排序
思路:先将数据二分直至一个数据,然后每次选择相邻的两组数据合并,保证左区间数据有序以及右区间数据有序,再从两个区间中按顺序取数,即合并
#include<bits/stdc++.h>
using namespace std;
int n,a[100009],tem[100009];
void msort(int l,int r)
{
if (l==r) return;
int mid=(l+r)/2,i=l,j=mid+1,k=l;
msort(l,mid);
msort(mid+1,r);
while(i<=mid&&j<=r)
{
if (a[i]<=a[j]) tem[k++]=a[i++];
else tem[k++]=a[j++];
}
while (i<=mid) tem[k++]=a[i++];
while (j<=r) tem[k++]=a[j++];
for (int i=l;i<=r;i++) a[i]=tem[i];
}
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
msort(1,n);
for (int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
效率与快速排序相当
7.堆排序
每次都保证根节点最小就行了,更新的时候每次都取最后一个数是因为要保证这棵树是完全二叉树
#include<bits/stdc++.h>
using namespace std;
int n,a[100009],cnt;
void put(int u)
{
int fa=u/2;
while(fa>=1)
{
if (a[u]<a[fa])
{
swap(a[u],a[fa]);
u=fa;
fa=u/2;
}
else break;
}
}
void update()
{
a[1]=a[cnt--];
int fa=1,son=fa<<1;
while(son<=cnt)
{
if (son<cnt&&a[son+1]<a[son]) son++;
if (a[fa]>a[son])
{
swap(a[fa],a[son]);
fa=son;
son<<=1;
}
else break;
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
cnt=i;
put(i);
}
for (int i=1;i<=n;i++)
{
cout<<a[1]<<" ";
update();
}
return 0;
}
这样写的缺点就是只可以有序输出,但是不可以将排序结果存储在原数组中。
为了解决这种尴尬的处境,还可以这么写:
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void update(int up,int down)
{
int i=up,j=i<<1,x=a[up];
while(j<=down)
{
if (j<down&&a[j]<a[j+1]) j+=1;
if (a[i]<a[j]) swap(a[i],a[j]);
else break;
i=j;
j=i<<1;
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i];
for (int i=n/2;i>=1;i--) update(i,n);
for (int i=n;i>=2;i--)
{
swap(a[i],a[1]);
update(1,i-1);
}
for (int i=1;i<=n;i++) cout<<a[i]<<" ";
return 0;
}
其中update(up,down)的作用是从节点up开始向下更新到节点down,保证以up为根的子树为大根堆,最后一个分支节点编号为n/2,这样也就保证了第一个update循环之后a[1]最大。最后一层循环逐个枚举,将最大的数安排到a[n],第二大的数安排到a[n-1],每次都记得update一次