步骤为:
- 从数列中挑出一个元素,称为 "基准"(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分割(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
void quick_sort(int s[], int l, int r)
{
if (l < r)
{
//Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
int i = l, j = r, x = s[l];
while (i < j)
{
while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
if(i < j)
s[i++] = s[j];
while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
i++;
if(i < j)
s[j--] = s[i];
}
s[i] = x;
quick_sort(s, l, i - 1); // 递归调用
quick_sort(s, i + 1, r);
}
}
【效率分析】
空间效率:快速排序是递归的,每层递归调用时的指针和参数均要用栈来存放,递归调用层次数与上述二叉树的深度一致。因而,存储开销在理想情况下为 O(log2n) ,即树的高度;在最坏情况下,即二叉树是一个单链,为O(n)。
时间效率:在n 个记录的待排序列中,一次划分需要约n 次关键码比较,时效为O(n),若设T(n)为对n 个记录的待排序列进行快速排序所需时间。
理想情况下:每次划分,正好将分成两个等长的子序列,则
T(n)≤cn+2T(n/2) c 是一个常数
≤cn+2(cn/2+2T(n/4))=2cn+4T(n/4)
≤2cn+4(cn/4+T(n/8))=3cn+8T(n/8)
······
≤cnlog2n+nT(1)= O(nlog2n)
最坏情况下:即每次划分,只得到一个子序列, 时效为O(n2)。
快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。 但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。 为改进之,通常以“三者取中法”来选取支点记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。