快速排序是一种最坏情况时间复杂度为o(n^2)的排序算法,虽然最坏情况时间复杂度很差,但是快速排序通常是实际应用中最好的选择,因为它的平均性能非常好,它的期望时间复杂度是o(nlgn),而且隐含的常数因子非常小。
一.基本思想
分解:将数组A[p..r]划分为两个子数组A[p..q-1],A[p+1..r](可能为空),使得A[p..q-1]均小于A[q],A[q+1]均大于A[q]
解决:递归调用快速排序,分别对两个字数组再进行排序
和平:因为子数组都是原址排序,所以不需要合并数组已经有序
二.代码实现
具体来说我们采用挖坑填数法,
1.i =Left; j = Right; 将基准数挖出形成第一个坑a[i]。
2.从j开始由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.从i开始由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
具体代码如下:
#include<iostream> using namespace std; int Partition(int *array,int i,int j) //排序并返回基准数正确位置 { int X=array[i]; //设定基准数 while(i<j) { while(i<j&&array[j]>=X) { --j; } if(array[j]<X){ array[i]=array[j]; } while(i<j&&array[i]<=X) ++i; if(array[i]>X){ array[j]=array[i]; } } array[i]=X; return i; } void QuickSort(int *array,int i,int j) { int p; if(i<j) { p=Partition(array,i,j); QuickSort(array,i,p-1); QuickSort(array,p+1,j); } } void Print(int *array, int length) { for(int i=0;i<length;i++) { cout<<array[i]<<" "; } cout<<endl; } int main() { int n,*array; cout<<"Please enter the number of the array: "; cin>>n; cout<<"Please enter the array now:"; for(int i=0;i<n;++i) { cin>>array[i]; } QuickSort(array,0,n-1); Print(array,n); return 0; }
三.算法时间复杂度
快速排序的运行时间依赖于划分是否平衡,而平衡与否又依赖于用于划分的元素。如果划分是平衡的,那么快速排序算法性能与归并排序一样。如果划分是不平衡的,那么快速排序的性能就接近于插入排序了。
最坏情况划分
当划分差生的两个子问题分别包含了n-1个元素和0个元素时,快速排序的最坏情况发生。假设每一次递归调用都产生了这种不平衡的划分。划分操作的时间复杂度是O(n),由于对一个大小为0的数组进行递归调用会直接返回,因此T(0)=O(1),于是算法运行的递归表达式为T(n)=T(n-1)+T(0)+O(n)=T(n-1)+O(n)。每一层递归的代价可以被累加起来,从而得到一个算术级数,其结果为O(n^2).因此,如果在算法的每一层递归上,划分都是最大程度不平衡的,那么算法的时间复杂度就是O(n^2)。
最好情况划分
在可能的最平衡的划分中,Partition得到的两个子问题的规模都不大于n/2,这是因为其中一个子问题的规模为n/2,另一个为n/2-1,。此时算法的运行时间的递归式为T(n)=2T(n/2)+O(n),解为T(n)=O(nlgn).
快速排序的平均运行时间更接近于最好情况,任何一种常熟比例的划分都会产生深度为O(lgn)的递归数,其中每一层的时间代价都为O(n),因此,只要划分是常数比例的,算法的运行时间总是O(nlgn).
所以,快速排序算法的平均时间复杂度为O(nlgn),最坏时间复杂度为O(n^2)。