时间空间复杂度
我们来分析一下快速排序法的性能。快速排序的时间性能取决于快速排序递归的深度,可以用递归树来描述递归算法的执行情况。如图9‐9‐7所示,它是{50,10,90,30, 70,40,80,60,20}在快速排序过程中的递归过程。由于我们的第一个关键字是50,正好是待排序的序列的中间值,因此递归树是平衡的,此时性能也比较好。
![]() |
图9-9-7 |
- T(n)≤2T(n/2) +n,T(1)=0
- T(n)≤2(2T(n/4)+n/2) +n=4T(n/4)+2n
- T(n)≤4(2T(n/8)+n/4) +2n=8T(n/8)+3n
- ……
- T(n)≤nT(1)+(log2n)×n= O(nlogn)
也就是说,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。
在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为 ,最终其时间复杂度为O(n2)。
平均的情况,设枢轴的关键字应该在第k的位置(1≤k≤n),那么:
![]() |
由数学归纳法可证明,其数量级为O(nlogn)。
就空间复杂度来说,主要是递归造成的栈空间的使用,最好情况,递归树的深度为log2n,其空间复杂度也就为O(logn),最坏情况,需要进行n‐1递归调用,其空间复杂度为O(n),平均情况,空间复杂度也为O(logn)。
可惜的是,由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。
快速排序原理
版本一:
原数组: 2 1 7 8 3 4 5
1.选中最后一个元素 5 为判断标准, 此时 bengin = 0 ; end = 6; i = -1; j 从0 到5
2. j = 0 : 判断A[0]小于5, i此时为0, 交换A[0]自身.
2 1 7 8 3 4 5
3. j = 1 : 判断A[1]小于5, i此时为1, 交换A[1]自身.
2 1 7 8 3 4 5
4. j = 2 : 判断A[2]大于5, i此时为1, 没有交换行为.
2 1 7 8 3 4 5
5. j = 3 : 判断A[3]大于5, i此时为1, 没有交换行为.
2 1 7 8 3 4 5
6. j = 4 : 判断A[4]小于5, i此时为2, 交换A[2]和A[4], 即交换7和3;
2 1 3 8 7 4 5
7. j = 5 : 判断A[5]小于5, i此时为3, 交换A[3]和A[5], 即交换8和4;
2 1 3 4 7 8 5
8. 完成循环, 交换A[3+1]和A[6];
2 1 3 4 5 8 7
此时数组分成 {2, 1, 3, 4}, {5}, {8,7} 三个部分.
Note: 由Step 2, 3 可以看出, 当开始的元素小于基准值时, 自身被迫与自身交换. 可以做简化至 判断 j 和 i当前是否相等 决定是否进行交换. 不过这事实上不会影响排序的效率.
快速排序算法的关键是PARTITION过程,它对A[p..r]进行就地重排:
PARTITION(A, p, r)
1 x ← A[r] //以最后一个元素,A[r]为主元
2 i ← p - 1
3 for j ← p to r - 1 //注,j从p指向的是r-1,不是r。
4 do if A[j] ≤ x
5 then i ← i + 1
6 exchange A[i] <-> A[j]
7 exchange A[i + 1] <-> A[r] //最后,交换主元
8 return i + 1
然后,对整个数组进行递归排序:
QUICKSORT(A, p, r)
1 if p < r
2 then q ← PARTITION(A, p, r) //关键
3 QUICKSORT(A, p, q - 1)
4 QUICKSORT(A, q + 1, r)
代码:
/*#include<iostream>//该快速排序是以最后元素为主元
//比较之后相交换
using namespace std;
void swap(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
}
int partition(int qs[],int f,int l)
{
int i,j,r;
r=qs[l];
i=f-1;
for(j=f;j<=l;j++)
{
if(qs[j]<=r)
{
++i;
swap(qs[i],qs[j]);
}
}
//swap(qs[i+1],qs[l]);
return i;
}
void quicksort(int qs[],int f,int l)
{
int q;
if(f<l) //这个条件很重要 否则递归会一直进行下去
{
q=partition(qs,f,l);
quicksort(qs,f,q-1);
quicksort(qs,q+1,l);
}
}
int main()
{
int n,i;
int *qs=new int[n];
cin>>n;
for(i=0;i<n;i++)
{
cin>>qs[i];
}
quicksort(qs,0,n-1);
for(i=0;i<n;i++)
{
cout<<qs[i]<<" ";
}
cout<<endl;
delete[] qs;
return 0;
}*/
版本二:
快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
该方法的基本思想是:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法:
先来看实例吧,定义下面再给出(最好能用自己的话来总结定义,这样对实现代码会有帮助)。
以一个数组作为示例,取区间第一个数为基准数。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
72 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 48 | 85 |
初始时,i = 0; j = 9; X = a[i] = 72
由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;
数组变为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 88 | 85 |
i = 3; j = 7; X=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。
数组变为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 42 | 60 | 72 | 83 | 73 | 88 | 85 |
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
对挖坑填数进行总结
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
代码:
#include<iostream>//该快速排序是以第一个元素为主元
//左右同时扫描
using namespace std;
void quicksort(int qs[],int f,int l)
{
if(f<l)
{
int i,j,k;
i=f;
j=l;
k=qs[i];
if(i<j)
{
while(i<j)
{
while(i<j && qs[j]>=k)
j--;
if(i<j) qs[i++]=qs[j];//从右向左找第一个小于k的数
while(i<j && qs[i]<k)
i++;
if(i<j) qs[j--]=qs[i];//从左向右找第一个大于k的数
}
}
qs[i]=k;
quicksort(qs,f,i-1);
quicksort(qs,i+1,l);
}
}
int main()
{
int i,n;
int *qs=new int[n];
cin>>n;
for(i=0;i<n;i++)
{
cin>>qs[i];
}
quicksort(qs,0,n-1);
for(i=0;i<n;i++)
{
cout<<qs[i]<<" ";
}
cout<<endl;
delete[] qs;
return 0;
}