题目描述
利用快速排序算法将读入的 N 个数从小到大排序后输出。
快速排序是信息学竞赛的必备算法之一。对于快速排序不是很了解的同学可以自行上网查询相关资料,掌握后独立完成。(C++选手请不要试图使用 STL,虽然你可以使用 sort 一遍过,但是你并没有掌握快速排序算法的精髓。)输入格式
第 1 行为一个正整数 N,第 2行包含 N 个空格隔开的正整数 ai,为你需要进行排序的数,数据保证了 Ai 不超过 10^9。
输出格式
将给定的 N 个数从小到大输出,数之间空格隔开,行末换行且无空格。
原题点击洛谷 快速排序
快排原理概述
下面以这个数组的排序为例
step1:完成对当前数组第一个数的排序,即将它放到它该在的位置(它左边的数都比它小,它右边的数字都比它大)
在这里我们先使用两个指针指向这个数组的头(left)和尾(right)
接下来的操作在一个死循环中进行
1.先从后往前扫(right指针不断左移),扫到第一个比temp(原数组首位)小的数,将它放到left指针指向的位置,空出当前right指针位置,同时left指针右移一格(这样可以保证右边的数都是比temp大的数,left指针右移原因看完后面自然懂)
2.接下来从左往右扫(left指针不断右移),扫到第一个比temp大的数,将它放到right指针指向的位置,空出当前left指针的位置,同时将right指针左移一格
由于上面两步操作在一个死循环中进行,所以当第二步结束后,又会重复执行第一步,最后当left指针于right指针相交时,死循环结束。
最后将temp的值放到left/right指针的位置
step2:对上一步排好序的数(6)左边的数组(4,1,2,5,3)和右边的数组(7,8,9,10)重复step1的操作
step3:当待排序的数组只剩一个元素或者少于1个元素的时候,结束操作。至此,一个简单的快排就已经完成了
分析:快排相当于将问题规模二分,每次操作排完一个数后,又将一个大数组分割成两个小数组再进行排序
下面附上未优化的快排代码
int split(int l,int r,int *a){//对分割数(数组首项)进行排序
int temp=a[l];
for(;;){
while(a[r]>=temp&&l<r)r--;
if(l>=r)break;
a[l]=a[r];
l++;
while(a[l]<=temp&&l<r)l++;
if(l>=r)break;
a[r]=a[l];
r--;
}
a[l]=temp;
return l;
}
void quickSort(int l,int r,int *a){
if(l>=r)return;
int mid=split(l,r,a);
quickSort(l,mid-1,a);
quickSort(mid+1,r,a);
}
但这样是过不了的,如果每次分割数的位置都是靠近边上的,O(nlogn)的算法会退化成O(n^2)
举个例子
10 9 8 7 6 5 4 3 2 1
每次分割将数组分割成长度为1和n-1的两个小数组,这样总共要分割n层
快排优化1——寻找合适的分割数
如果我们每次分割的位置都在数组正中间附近,数组的规模很快就会变小,总分割层数也会降低。
选择分割数的方法很多,这里仅介绍三元素取中法
即每次选取数组的头尾和中间项,三个数比大小,选取中间的数当分割数。(需要将其与首项进行换位)。下面附上代码
void threeMid(int l,int r,int *a){
int mid=(l+r)>>1;
if((a[l]>a[mid]&&a[mid]>a[r]) || (a[l]<a[mid]&&a[mid]<a[r]))
{
swap(a[l],a[mid]);
}
else if((a[l]>a[r]&&a[r]>a[mid])||(a[mid]>a[r]&&a[r]>a[l]))
{
swap(a[l],a[r]);
}
}
将其放到split最前面即可
但一个数组中难免有许多相同的数,当相同的数字很多的时候,O(nlogn)的算法同样会退化成O(n^2)
例如:
6 6 6 6 6 6 6 6 6 6
快排优化2——解决重复键问题
我们的目标是一次性排序完所有和分割数相同的数
我们的方法是每次扫到和temp(分割数)相同的数的时候,就将其放到最边上(从左往右扫 扫到放左边,从右往左扫 扫到放最右边)。最后当指针相遇时,将最边上的数放回中间。下面附代码
void split(int &left,int &right,int *a){
//这里我们需要两组指针,一组用来正常扫,另一组用来存边上相同的数
int l=left,r=right,ll=left,rr=right;
threeMid(l,r,a);
int temp=a[l];
for(;;){
while(a[r]>=temp&&l<r){
if(a[r]==temp){//将相同数放到最右边
swap(a[r],a[rr]);
rr--;
}
r--;
}
if(l>=r)break;
a[l]=a[r];
l++;
while(a[l]<=temp&&l<r){
if(a[l]==temp){//将相同数放到最左边
swap(a[l],a[ll]);
ll++;
}
l++;
}
if(l>=r)break;
a[r]=a[l];
r--;
}
a[l]=temp;
for(int i=left;i<ll;i++){//将相同数从最左边换到temp的左边
swap(a[i],a[l-1]);
l--;
}
for(int i=right;i>rr;i--){//将相同数从最右边换到temp的右边
swap(a[i],a[r+1]);
r++;
}
//从l到r都是相同的数。此处需要同时传出去两个位置,即l-1和r+1
right=r+1;
left=l-1;
}
最后附上完整AC代码
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=1e5+10;
int a[maxn];
void threeMid(int l,int r,int *a){
int mid=(l+r)>>1;
if((a[l]>a[mid]&&a[mid]>a[r]) || (a[l]<a[mid]&&a[mid]<a[r])){
swap(a[l],a[mid]);
}
else if((a[l]>a[r]&&a[r]>a[mid])||(a[mid]>a[r]&&a[r]>a[l])){
swap(a[l],a[r]);
}
}
void split(int &left,int &right,int *a){
//这里我们需要两组指针,一组用来正常扫,另一组用来存边上相同的数
int l=left,r=right,ll=left,rr=right;
threeMid(l,r,a);
int temp=a[l];
for(;;){
while(a[r]>=temp&&l<r){
if(a[r]==temp){//将相同数放到最右边
swap(a[r],a[rr]);
rr--;
}
r--;
}
if(l>=r)break;
a[l]=a[r];
l++;
while(a[l]<=temp&&l<r){
if(a[l]==temp){//将相同数放到最左边
swap(a[l],a[ll]);
ll++;
}
l++;
}
if(l>=r)break;
a[r]=a[l];
r--;
}
a[l]=temp;
for(int i=left;i<ll;i++){//将相同数从最左边换到temp的左边
swap(a[i],a[l-1]);
l--;
}
for(int i=right;i>rr;i--){//将相同数从最右边换到temp的右边
swap(a[i],a[r+1]);
r++;
}
//从l到r都是相同的数。此处需要同时传出去两个位置,即l-1和r+1
right=r+1;
left=l-1;
}
void quickSort(int l,int r,int *a){
if(l>=r)return;
int left=l,right=r;
split(left,right,a);
quickSort(l,left,a);
quickSort(right,r,a);
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
quickSort(1,n,a);
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
return 0;
}
附上博主b站直播号:22454049。直播新人求关注>_<