快速排序也是基于比较进行的排序,他的原理是将未排序元素根据一个作为基准的“主元”(pviot)分为两个子序列,其中一个子序列的元素均小于主元,另一个均大于主元,然后递归的对这两个子序列进行排序。本质上,快速排序使用的仍然是分治法,将问题的规模不断缩小,然后分别进行处理
我们将比主元大的元素从右向左放置,比主元小的元素从右向左放置,算法步骤如下:
- 选择一个主元,并与最后一个元素交换(交换是为了方便的确定扫描区间,Left+1~Right-2)
- 设置两个指针Low和High,初值分别指向第一个和倒数第二个元素
- Low从左向右扫描,它的左侧全是比他小的元素,High从右向左扫描,它的右侧全是比他大的元素。每当Low遇到比主元大的元素,就停下,High遇到比主元小的元素,也会停下。
- 如果Low<High,那么二者指向的元素互换位置
- 重复第3、4步,直到Low和High错位(Low>High),将主元和Low指向的元素交换位置,这就完成了一次划分,以主元为边界,左边都比他小,右边都比他大(也就是一次性抵达了该到的位置)
- 递归的对主元左侧子序列,右侧子序列用同样方法进行快排
快排的时间复杂度:快排的时间复杂度略显复杂,在最好的情况下,每一次都恰好把序列一分为二,那么整个算法的递归深度就是logN,而在每一层递归上,Low和High都要从各自的起点出发,朝相反方向扫描,直到二者错位为止,也就是整个序列都要进行一次扫描并进行比较,也就是O(N)。那么整个算法在最好情况下,时间复杂度是O(NlogN)
整个算法最坏的情况,是在pviot的选择上出现了问题,比如太大或者太小,会导致分出的两个子集一边很大一边很小,这就失去了分治的意义。所以为了避免这种最坏主元的情况,我们可以设置一些方法来选择主元,比如选择前3个元素中既不是最大也不是最小的那个,这样避免了会选择到最差的那一个
另外一个问题是,由于快速排序使用的是递归实现的方式,待排序序列太小时,递归的副作用就会凸显,效果甚至还不如简单的插入排序,所以我们可以在快排中增加一个规模判定,当子问题缩小到一定地步,就不再递归,而是直接调用插入排序解决问题。
快排为什么快呢?以插入排序为例,我们每一次插入一个元素,此时元素所处的位置都是临时的(最后一次除外),但快排不同,快排一旦选定了pviot,它在整个序列中的位置就唯一确定了。快排的过程也是不断选择pviot的过程,换而言之,就是每一次pviot选择函数的调用,都会唯一的确定一个元素的位置
代码如下:
ElementType Median3(ElementType A[],int Left,int Right)//pivot选择
{
int Center=(Left+Right);
if(A[Left]>A[Center])
swap(A[Left],A[Center]);
if(A[Left]>A[Right])
swap(A[Left],A[Right]);
if(A[Center]>A[Right])
swap(A[Center],A[Right]);//此时A[Left]<=A[Center]<=A[Right];
swap(A[Left],A[Right-1]);//把基准pivot换到右边
return A[Right-1];//返回基准pivot
}
void Qsort(ElementType A[],int Left,int Right)
{
int Pivot,Cutoff,Low,High;
if(Cutoff<=Right-Left)//如果元素足够多,使用快排
{
Pivot=Median3(A,Left,Right);
Low=Left;
High=Right-1;
//仔细观察这个赋值方式和循环,会发现函数只扫描了Left-1到Right-2
//事实上,Left已经在选Pivot的过程中安排明白了,Right-1则是pivot
while(1)
{
while(A[++Low]<Pivot);
while(A[--High]>Pivot);
if(Low<High)
//两个指针从左右端开始,向中间扫描
//当遇到一个大于pivot,一个小于Pivot的元素的时候,交换二者的位置
swap(A[Low],A[High]);
else
break;
}//这一趟实际上确定了pivot的最终位置,并放到了A[low]上
swap(A[Low],A[Right-1]);
Qsort(A,Left,Low-1);
Qsort(A,Low+1,Right);//递归解决左右
}
else InsertionSort(A+Left,Right-Left+1);//元素较少,直接使用插入排序
}
void QuickSort(ElementType A[],int N)//统一接口
{
Qsort(A,0,N-1);
}
void InsertionSort(int A[],int N)
{
int P,i;
ElementType Tmp;
for(P=1;P<N;P++)
{
Tmp=A[P];
for(i=P;i>0&&A[i-1]>Tmp;i--)
A[i]=A[i-1];
A[i]=Tmp;
}
}