快速排序(优化版)
本题来自洛谷(快速排序)
原题地址(https://www.luogu.org/problemnew/show/P1177)
直接进入正题。
快速排序的大致思路:先找到一个基数(随便找,但是有一般有一个习惯),基数用于与所有数比较,使左边的数比基数小,右边的数比基数大,把左边和右边的数看作新的数列,再进行新的排序。当左右的数列只有一个数结束。(这个算法的的时间复杂度是不确定的)
图片来自《啊哈!算法》
原数列为6,1,2,7,9,3,4,5,10,8。
第一步:
一般把最左边的数作为基数(temp),把最左边数设为i,把最右边的数设为j,现在从最右边(j)开始找,因为你需要一个结束条件(为i,j同时指到一个数),最后你要把你选择的基数(6)和中间的分艰线的值进行交换,所以,你要交换的数必须比基数(6)小,上一次交换后i指的数是比6小的数(4),当j走到与i相同时,不再走,这时i,j都指的是比基数(6)小的数这样进行交换才能保证交换后的数列变成我们想要的样子。选择的数都为比6小的数,你想:如果要把基数(6)右边的数都大于基数(6),基数(6)左边的数小于基数(6),你应该怎么办?
显然当你(j)往左走时,遇到比6小的数停下,等着他的兄弟(i)找到一个比6大的数,他们的数交换;当遇到两个数相同时(i=j),再把i位和最左位的数进行交换就行了。
第二步:
再把基数(6)左的数组右面的数组分别进行排序,直到左边界等于或者大于右边界。
本以为这道题就做完了,但是5个样例只通过了1个,其余4个为超时。这时就要对代码进行优化。
第一步优化(基数随机化):
原本我们找的数为最左边的数,如果现在我们的数列为从大到小的顺序,你就要一个一个的进行排序,时间复杂度退化为O(n^2),还是会超时,这时我们就把基数进行随机化,基数取数列中任意一个,这样可以大大减少时间。
第二步优化(小序列简单排序):
快速排序虽然快速,但是当序列小的时候要进行非常多次递归,这样时间会增加很多,而一些简单的排序(冒泡排序,选择排序)在小序列时更加的快速。(如上图)
第三步优化(除去相等的数):
当进行一次排序以后,如果与基数相邻的数都为基数相同,那么就不必再把相同的数列入下一个序列进行排列了,比如:基数为中间的8时,两旁的8就没有必要再进行排序了。
综合以上方法写出了代码,也是能全部AC的。
#include<bits/stdc++.h>
using namespace std;
long int f[100005],temp,i,j;
void swap(long int &a,long int &b)
{
long int n=a;
a=b;
b=n;
}
void asort(int left,int right)
{
for(int i=left;i<=right;i++)
{
for(int j=i;j<=right;j++)
{
if(f[j]<f[i])
swap(f[i],f[j]);
}
}
return;
}
void qsort(int left,int right){
if(left>=right) return;
if(right-left<=10)
{
asort(left,right);
return;
}
temp=rand()% (right - left + 1) + left;swap(f[temp],f[left]);
i=left;
temp=f[left];
j=right;
while(i!=j)
{
while(f[j]>=temp&&i<j)
j--;
while(f[i]<=temp&&i<j)
i++;
if(i<j)
{
swap(f[i],f[j]);
}
}
{
f[left]=f[i];
f[i]=temp;
}
while(f[i]==temp&&i>=left)
i--;
qsort(left,i);
while(f[j]==temp&&j<=right)
j++;
qsort(j,right);
}
int main()
{
int n;
scanf("%d",&n);
for (int i = 0; i < n; i++)
scanf("%d", &f[i]);
qsort(0,n-1);
for (int i = 0; i < n; i++)
printf("%d ", f[i]);
return 0;
}