快速排序
1.举个例子
首先我们给出组数据:6 1 2 7 9 3 4 5 10 8
接下来,我们的目的是将他们从小到大排序;
首先:我们设立一个基准数(也就是这组数据的第一个数,6),接下来,我们需要把比基准数6大的数全部放到6的右边,比基准数6小的数全部放到6的左边(也就是将基准数放在中间某个位置使得基准数左边的数全部小于他,基准数右边的数全部大于他)
那么我们该如何实现这个过程呢?
我们可以用交换的方法,我们从右边开始,寻找一个比6小的数,再从左边寻找一个比6大的数,然后交换他们的位置
这里我们需要用到两个小可爱:i 和 j;让他们分别从左右两端开始寻找符合要求的数
看下图
6 | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 |
---|---|---|---|---|---|---|---|---|---|
i | j |
小可爱 j 开始移动(每次必须是从 j 开始!)
6 | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 |
---|---|---|---|---|---|---|---|---|---|
i | j |
找到一个比6小的数了,然后就是 i 的工作了
6 | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 |
---|---|---|---|---|---|---|---|---|---|
i | j |
交换!
6 | 1 | 2 | 5 | 9 | 3 | 4 | 7 | 10 | 8 |
---|---|---|---|---|---|---|---|---|---|
i | j |
我们发现 i 和 j 并没有相遇,那么我们继续,我们发现9>6,4<6,果断交换!
6 | 1 | 2 | 5 | 4 | 3 | 9 | 7 | 10 | 8 |
---|---|---|---|---|---|---|---|---|---|
i | j |
接下来,我们的小可爱 i 和 j 终于见到了彼此,那么我们就将 3 和 基准数6 交换,这样我们就找到了属于 6 的那个专属位置 k !
3 | 1 | 2 | 5 | 4 | 6 | 9 | 7 | 10 | 8 |
---|---|---|---|---|---|---|---|---|---|
i j |
到此第一轮“探测”真正结束。现在基准数6已经归位,此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵 j 的使命就是要找小于基准数的数,而哨兵 i 的使命就是要找大于基准数的数,直到 i 和 j 碰头为止。
现在我们将第一轮“探测"结束后的序列,就需要用到二分的思想。以6为分界点拆分成两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。
第一轮探测后,我们得到以下序列
2 | 1 | 3 | 5 | 4 |
---|
接下来我们又以 3 为分界点
1 | 2 | 3 | 4 | 5 |
---|
如此便将 6 左边的序列排好序,右边同样如此;
由此我们可以发现,每一轮排序的过程,就是将基准数归位的过程,直到将每一位基准数归位,这个过程就结束了。
接下来以《啊哈算法》里面的图示来展现:
在最坏的情况下,可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和
冒泡排序是一样的,都是 O(N2),它的平均时间复杂度为 O (NlogN)。其实快速排序是基于一种叫做“二分”的思想。
2.代码实现
下面是这个算法的代码实现
void quicksort(int left,int right)
{
if(left>right)
return ;//判断是否错误
int i=left,j=right;
int temp=a[left];//temp 用来存储基准数
while(i!=j)//当i,j没有相遇时
{
while(a[j]>=temp&&i<j)//直到寻找到一个比基准数大的数
j--;
while(a[i]<=temp&&i<j)//直到寻找到一个比基准数大的数
i++;
if(i<j)//必须满足i<j
swap(a[i],a[j]);//swap是C++中自带的,当然,C语言可以变量赋值交换
}
swap(a[i],a[left]);//基准数归位
quicksort(left,i-1);//继续归位分界点左边的序列
quicksort(i+1,right);//继续归位分界点右边的序列
//实际上是一个递归和二分的过程
}
假如我们输入以下数据
10
6 1 2 7 9 3 4 5 10 8
得到的结果就是
1 2 3 4 5 6 7 8 9 10
下面就是这个序列变化的过程(带*号的是基准数)
6 1 2 7 9 3 4 5 10 8
3 1 2 5 4 *6* 9 7 10 8
2 1 *3* 5 4 6 9 7 10 8
1 *2* 3 5 4 6 9 7 10 8
*1* 2 3 5 4 6 9 7 10 8
1 2 3 4 *5* 6 9 7 10 8
1 2 3 *4* 5 6 9 7 10 8
1 2 3 4 5 6 8 7 *9* 10
1 2 3 4 5 6 7 *8* 9 10
1 2 3 4 5 6 *7* 8 9 10
1 2 3 4 5 6 7 8 9 *10*
文末附上完整代码
#include<bits/stdc++.h>//万能头文件真香
using namespace std;
int a[505];//这里我们定义全局变量,函数中方可调用
int n;
void quicksort(int left,int right)
{
if(left>right)
return ;//判断是否错误
int i=left,j=right;
int temp=a[left];//temp 用来存储基准数
while(i!=j)//当i,j没有相遇时
{
while(a[j]>=temp&&i<j)//直到寻找到一个比基准数大的数
j--;
while(a[i]<=temp&&i<j)//直到寻找到一个比基准数大的数
i++;
if(i<j)//必须满足i<j
swap(a[i],a[j]);//swap是C++中自带的,当然,C语言可以变量赋值交换
}
swap(a[i],a[left]);//基准数归位
quicksort(left,i-1);//继续归位分界点左边的序列
quicksort(i+1,right);//继续归位分界点右边的序列
//实际上是一个递归和二分的过程
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
quicksort(1,n);//调用函数
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
return 0;
}