1.概述
快速排序是最慢的冒泡排序的升级,它们都属于交换排序类。也是通过不断比较的移动交换来实现交换的,与冒泡排序相比,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动交换次数。
基本思想是:
通过一趟排序将待排序记录分割成成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。与归并排序一样,它也是一种分治的递归算法。
2.实现
基本实现:
void swap(int A[],int i,int j)
{
int tmp=A[j];
A[j]=A[i];
A[i]=tmp;
}
//找出枢纽元的位置
int Partition(int A[],int left,int right)
{
int privotkey;
privotkey=A[left];
while(left<right)
{
while(left<right&&A[right]>=privotkey)
right--;
swap(A,left,right);//从右开始找一个比枢纽元小的元素交换到底端
while(left<right&&A[left]<=privotkey)
left++;
swap(A,left,right);//从左开始找一个比枢纽元大的元素交换到高端
}
return left;
}
void QSort(int A[],int left,int right)//待排序序列的最小下标值left和最大下标值right
{
int priovt;
if(left<right)
{
priovt=Partition(A,left,right);
QSort(A,left,priovt-1);
QSort(A,priovt+1,right);
}
}
void QuickSort(int A[],int N)
{
QSort(A,0,N-1);
}
Partition函数的作用,从当前序列当中选取一个关键字,然后把它放到一个位置,使得它左边的值都比它小,右边的值比它大,这样的关键字称为枢纽元。
快速排序优化:
(1)优化选取枢纽元
通常没有经过考虑的选择就是将第一个元素用作枢纽元。如果输入是随机的那么还可以接受,但是如果输入是预排序或者反序的,那么这样的枢纽元选择就会产生一个恶劣的分割。
随机选取枢纽元:除了极端情况,一般来说是比较安全的,但是随机数的生成一般是比较昂贵的。
三数中值分割法:一组N个数的中值是第[N/2]个最大的数,一般的做法是,使用左端,右端和中心位置的三个元素的中值作为枢纽元,使用这个方法可以消除预排序输入的坏情况。
//找出枢纽元的位置
int Partition(int A[],int left,int right)
{
int privotkey;
int center=left+(right-left)/2;
if(A[left]>A[right])
swap(A,left,right);
if(A[center]>A[right])
swap(A,center,right);
if(A[left]<A[center])
swap(A,center,left);
privotkey=A[left];
while(left<right)
{
while(left<right&&A[right]>=privotkey)
right--;
swap(A,left,right);//从右开始找一个比枢纽元小的元素交换到底端
while(left<right&&A[left]<=privotkey)
left++;
swap(A,left,right);//从左开始找一个比枢纽元大的元素交换到高端
}
return left;
}
(2)优化不必要的交换
//找出枢纽元的位置
int Partition(int A[],int left,int right)
{
int privotkey;
int center=left+(right-left)/2;
if(A[left]>A[right])
swap(A,left,right);
if(A[center]>A[right])
swap(A,center,right);
if(A[left]<A[center])
swap(A,center,left);
privotkey=A[left];
int tmp=privotkey;
while(left<right)
{
while(left<right&&A[right]>=privotkey)
right--;
//swap(A,left,right);//从右开始找一个比枢纽元小的元素交换到底端
A[left]=A[right];//采用替换而不是交换来进行操作
while(left<right&&A[left]<=privotkey)
left++;
//swap(A,left,right);//从左开始找一个比枢纽元大的元素交换到高端
A[right]=A[left];
}
A[left]=tmp;
return left;
}
(3)优化小数组时的排序方案
如果数组非常小,快速排序不如直接插入排序更好,直接插入排序是简单排序中性能最好的。原因是快速排序用到了递归操作,在大量数据排序时,这点性能影响对于它的整体算法优势而言是可以忽略的。
void QSort(int A[],int left,int right)
{
int priovt;
if(right-left>MAX)
{
priovt=Partition(A,left,right);
QSort(A,left,priovt-1);
QSort(A,priovt+1,right);
}
else
insertaion_sort(A,right-left+1);
}
MAX的值不是一定的。
(4)尾递归优化
之前的QSort在其尾部有两次递归操作,栈的大小是有限的,每次递归操作都会耗费一定的栈空间,函数的参数越多,每次耗费的空间也就越多。如果可以减少递归,将会大大提高性能。
void QSort(int A[],int left,int right)
{
int priovt;
if(right-left>MAX)
{
while(left<right)
{
priovt=Partition(A,left,right);
QSort(A,left,priovt-1);
left=priovt+1;
}
}
else
insertaion_sort(A,right-left+1);
}
3.复杂度分析
time=O(nlgn),由于其关键字的比较和交换是跳跃的,因此,快速排序是一种不稳定的排序方法。