摘自《数据结构(C语言版)》–严蔚敏
快速排序(Quick Sort)是对冒泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以到达整个序列有序。
假设待排序的序列为{L.r[s],L.r[s+1],…,L.r[t]},首先任意选取一个记录(通常可选第一个记录L.r[s])作为枢轴(或支点)(pivot),然后按下述原则重新排序其余记录:将所有关键字较它小的记录都安置在它的位置之前,将所有关键字较它大的记录都安置在它的位置之后。由此可以该“枢轴”记录最后所落位置i作为分界线,将序列{L.r[s],L.r[s+1],…,L.r[t]}分割成两个子序列{L.r[s],L.r[s+1],…,L.r[i-1]}和{L.r[i+1],L.r[i+2],…,L.r[t]}。这个过程称为一趟快速排序(或一次划分)。
一趟快速排序的具体做法是,附设两个指针low和high,它们的初值分别为low和high,设枢轴记录的关键字为pivotket,则首先从high所指位置起向前搜索找到第一个关键字小于pivotkey的记录和枢轴记录相互交换,然后从low所指位置起向后搜,找到第一个关键字大于pivotkey的记录和枢轴记录相互交换,重复这两步直至low=high为止。其算法如下所示:
int Partition(SqList &L,int low,int high) {
//交换顺序表L中字表L[low...high]的记录,使枢轴记录到位,并返回其所在位置,此时
//在它之前(后)的记录均不大(小)于它。
pivotkey=L.r[low].key; //用子表的第一个记录作枢轴记录
while(low<high) { //从子表的两端交替地向中间扫描
while(low<high && L.r[high].key>=pivotkey) --high;
L.r[low]←→L.[high]; //将比枢轴记录小的记录交换到低端
while(low<high && L.r[low].key<=pivotkey) ++low;
L.r[low]←→L.[high]; //将比枢轴记录大的记录交换到高端
}
return low; //返回枢轴所在的位置
}//Partition
具体实现上述算法时,每交换一对记录需进行3次记录移动(赋值)的操作。而实际上,在排序过程中对枢轴记录的赋值是多余的,因为只有在一趟排序结束时,即low=high的位置才是枢轴记录的最后位置。由此可改写上述算法,先将枢轴记录暂存在r[0]的位置上,排序过程中只作r[low]或r[high]的单向移动,直到一趟排序结束后再将枢轴记录移至正确位置上。算法如下所示:
int Partition(SqList &L,int low,int high) {
//交换顺序表L中字表L[low...high]的记录,使枢轴记录到位,并返回其所在位置,此时
//在它之前(后)的记录均不大(小)于它。
L.r[0]=L.r[low]; //用子表的第一个记录作枢轴
pivotkey=L.r[low].key; //枢轴记录关键字
while(low<high) { //从子表的两端交替地向中间扫描
while(low<high && L.r[high].key>=pivotkey) --high;
L.r[low]=L.[high]; //将比枢轴记录小的记录移到低端
while(low<high && L.r[low].key<=pivotkey) ++low;
L.r[low]=L.[high]; //将比枢轴记录大的记录移到高端
}
L.r[low]=L.r[0]; //枢轴记录到位
return low; //返回枢轴所在的位置
}//Partition
在清楚一趟快速排序的过程时,整个快速排序的过程就很容易用递归的方法写出。若待排序列中只有一个记录,显然已经有序,否则进行一趟快速排序后再分别对分割所得的两个子序列进行快速排序,递归形式的快速排序算法如下所示:
void Qsort(SqList &L,int low,int high) {
//对顺序表L中的子序列L.r[low...high]作快速排序
if(low<high) { //长度大于1
pivotloc=Partition(L,low,high); //将L.r[low...high]一分为二
Qsort(L,low,pivotloc-1); //对低子表递归排序,pivotloc是枢轴位置
Qsort(L,pivotloc+1,high); //对高子表递归排序
}
}//Qsort
void QuickSort(SqList &L) {
//对顺序表L作快速排序
Qsort(L,1,L.length);
}//QuickSort
通常快速排序被认为是,在所有同数量级 O(nlogn) 的排序方法中,其平均性能最好,但是,若初始记录序列按关键字有序或基本有序时,快速排序将蜕化为冒泡排序,其时间复杂度为 O(n2) 。为改进之,通常依“三者取中”的法则来选取枢轴记录,记比较 L.r[s].key、L.r[t].key、L.r[(s+t)/2].key ,取三者中其关键字取中值的记录为枢轴,只要将该记录和L.r[s].key互换,算法不变。经验证明,采取三者取中的规律可大大改善快速排序在最坏情况下的性能。然而,即使如此,也不能使快速排序在待排记录已按关键字有序的情况下到达 O(n) 的时间复杂度。为此,可如下所述修改“一次划分”算法:在指针域high减1和low增1的同时进行“冒泡”操作,即在相邻两个记录处于“逆序”时进行互换,同时在算法中附设两个布尔型变量分别指示指针low和high在从两端向中间移动过程中是否进行过交换记录的操作,若指针low在从低端向中间的移动过程中没有进行交换记录的操作,则不再需要对低端子表进行排序;类似的,若指针high在从高端向中间的移动过程中没有进行交换记录的操作,则不再需要对高端子表进行排序。显然,如此“划分”将进一步改善快速排序的平均性能。