1. 算法介绍
快速排序是基于 分治 理念的一种算法,其基于冒泡排序,平均时间复杂度可以达到O(nlog2n)。
值得一提的是,该算法并不稳定。并且在极端情况下,时间复杂度可以达到O(n2)。
2. 算法思想
-
在一轮快速排序中,将需要首先在数组中选取一个基准数字
x
。一般情况下,基准数字可以是左边界,右边界,中值或随机一个数组中的数字。 -
引用两个指针
i
和j
,分别指向左边界和右边界。 -
i
指针 向右 查询第一个 不小于x 的元素,而j
指针则 向左 查询第一个 不大于x的元素,然后将两者互换。-
注意:在这类快排思想中,当i或j指针恰好指针指到了与x相等的值,则其也会停下!
-
也就是说,若二者均指到了与相等的值,则其也会进行一次互换,虽然结果与原先相同!
-
-
随后,将
i
、j
指针向下移动一位。 -
重复上述两个步骤,直至
i
、j
两指针相遇,此时确认了x
的位置。-
注意:在快速排序过程中,i指针的左侧元素必然不大于x,而j指针的右侧必然不小于x!
-
-
利用快速排序,递归
x
的左子区和右子区,最终完成排序。
3. 算法模板
void quick_sort(int q[],int l,int r){
if(l >= r) return;//(1)
int x = q[l] , i = l - 1 , j = r + 1; //(2)
while(i < j){
do i++ ; while(q[i] < x); //(3)
do j-- ; while(q[j] > x); //(4)
if(i < j) swap(q[i],q[j]);
}
quick_sort( q[] , l , j ); //(5)
quick_sort( q[] , j + 1 , r); //(6)
return;
}
(1) 如果,左边界大于等于右边界,说明此时数组内元素个数为0或1,此时不需要排序,直接返回结果。
(2)i = l - 1 , j = r + 1
,此处这样处理的理由如下:
观察下列代码,不难发现其利用do—while语句进行循环,这意味着i
、j
两指针首先进行 下移一位 的操作,如此初始化i
、j
两指针,首先下移一位,则i
、j
指向了真正的数组边界,从而实现正确的排序结果。但,如果此处仅仅将其置为i = l, j = r
,将i
、j
两指针下移,则会错过边界值的判断。当数组下标较少时,甚至会报错。
(3)依据算法思想,i
的任务就是找到第一个不小于x
的值,其中也包括x
本身。当do语句里的i++
语句完称后,此时i
指针已经指向了这个 不小于x
的值,同时,如果q[i] = x
,i
指针也将指向该值。
若将do-while循环条件改为q[i]<=x
,此时将会在上述基础上情况多循环一轮,那么当q[i]=x
时,i
将指向值为x
元素的下一个元素。这意味着,如果x
恰巧取于右边界,数组将会越界。
(4)与(3)相同。
(5)此处是一个更新。递归调用,继续快速排序其左子区和右子区。
4. 算法难点
该算法难于理解的是x的取值和递归语句的使用。
从结论上来说:
如果,
x=q[l]
,则后续递归语句必须写为:
quick_sort(q,l,j);quick_sort(q,j+1,r)
如果,
x=q[r]或x=q[(l+r+1)/2]
,则后续递归语句必须写为:
quick_sort(q,l,i-1);quick_sort(q,i,r)
4. 算法实现
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int q[N];
int num = 0 ;
void quick_sort(int q[],int l,int r){
if(l >= r) return;//(1)
int x = q[l] , i = l - 1 , j = r + 1; //(2)
while(i < j){
do i++ ; while(q[i] < x); //(3)
do j-- ; while(q[j] > x); //(4)
if(i < j) swap(q[i],q[j]);
}
quick_sort( q , l , j ); //(5)
quick_sort( q , j + 1 , r); //(6)
return;
}
int main(){
scanf("%d",&num);
for(int i = 0 ; i < num ; i++){
scanf("%d",&q[i]);
}
quick_sort( q , 0 , num - 1);
for(int i = 0 ; i < num ; i++){
printf("%d ",q[i]);
}
return 0;
}