快速排序也是一种采用分治法解决问题的一个典型应用。在很多编程语言中,对数组,列表进行的非稳定排序在内部实现中都使用的是快速排序。而且快速排序在面试中经常会遇到。
本文首先介绍快速排序的思路,算法的实现、分析、优化及改进,最后分析了.NET 中列表排序的内部实现。
一 原理
快速排序的基本思想如下:
对数组进行随机化。
从数列中取出一个数作为中轴数(pivot)。
将比这个数大的数放到它的右边,小于或等于它的数放到它的左边。
再对左右区间重复第三步,直到各区间只有一个数。
如上图所示快速排序的一个重要步骤是对序列进行以中轴数进行划分,左边都小于这个中轴数,右边都大于该中轴数,然后对左右的子序列继续这一步骤直到子序列长度为1。
下面来看某一次划分的步骤,如下图:
上图中的划分操作可以分为以下5个步骤:
获取中轴元素
i从左至右扫描,如果小于基准元素,则i自增,否则记下a[i]
j从右至左扫描,如果大于基准元素,则i自减,否则记下a[j]
交换a[i]和a[j]
重复这一步骤直至i和j交错,然后和基准元素比较,然后交换。
划分过程的代码实现如下:
///
/// 快速排序中的划分过程
///
/// 待划分的数组
/// 最左侧位置
/// 最右侧位置
/// 中间元素位置
private static int Partition(T[] array, int lo, int hi)
{
int i = lo, j = hi + 1;
while (true)
{
//从左至右扫描,如果碰到比基准元素array[lo]小,则该元素已经位于正确的分区,i自增,继续比较i+1;
//否则,退出循环,准备交换
while (array[++i].CompareTo(array[lo]) < 0)
{
//如果扫描到了最右端,退出循环
if (i == hi) break;
}
//从右自左扫描,如果碰到比基准元素array[lo]大,则该元素已经位于正确的分区,j自减,继续比较j-1
//否则,退出循环,准备交换
while (array[--j].CompareTo(array[lo]) > 0)
{
//如果扫描到了最左端,退出循环
if (j == lo) break;
}
//如果相遇,退出循环
if (i >= j) break;
//交换左a[i],a[j]右两个元素,交换完后他们都位于正确的分区
Swap(array, i, j);
}
//经过相遇后,最后一次a[i]和a[j]的交换
//a[j]比a[lo]小,a[i]比a[lo]大,所以将基准元素与a[j]交换
Swap(array, lo, j);
//返回扫描相遇的位置点
return j;
}
划分前后,元素在序列中的分布如下图:
二 实现
与合并算法基于合并这一过程一样,快速排序基于分割(Partition)这一过程。只需要递归调用Partition这一操作,每一次以Partition返回的元素位置来划分为左右两个子序列,然后继续这一过程直到子序列长度为1,代码的实现如下:
public class QuickSort where T : IComparable
{
public static void Sort(T[] array)
{
Sort(array, 0, array.Length - 1);
}
private static void Sort(T[] array, int lo, int hi)
{
//如果子序列为1,则直接返回
if (lo >= hi) return;
//划分,划分完成之后,分为左右序列,左边所有元素小于array[index],右边所有元素大于array[index]
int index = Partition(array, lo, hi);
//对左右子序列进行排序完成之后,整个序列就有序了
//对左边序列进行递归排序
Sort(