冒泡排序在扫描过程中只对相邻的两个元素进行比较,因此在互换两个相邻元素时只能消除一个逆序。如果通过两个不相邻元素的交换能够消除待排序记录中的多个逆序,则会大大加快排序的速度。快速排序(Quick Sort)正是通过不相邻元素交换而消除多个逆序的,因而可以认为其是冒泡排序的升级版。
快速排序是由C.A.R Hoare提出并命名的一种排序方法,在目前各种排序方法中,这种方法对元素进行比较的次数较少,因而速度也比较快,被认为是目前最好的排序方法之一。在.NET中的多个集合类所提供的Sort()方法中都使用了快速排序对集合中的元素进行排序。
快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
快速排序的核心步骤为:
①获取中轴元素
②i从左至右扫描,如果小于基准元素,则i自增,否则记下a[i]
③j从右至左扫描,如果大于基准元素,则i自减,否则记下a[j]
④交换a[i]和a[j]
⑤重复这一步骤直至i和j交错,然后和基准元素比较,然后交换。
(1)主入口:获取索引并对左右两个区间进行递归操作
public static void QuickSort(T[] arr, int low, int high) { if (low < high) { int index = Partition(arr, low, high); // 对左区间递归排序 QuickSort(arr, low, index - 1); // 对右区间递归排序 QuickSort(arr, index + 1, high); } }
(2)核心:获取基准值的实际存储位置
private static int Partition(T[] arr, int low, int high) { int i = low, j = high; T temp = arr[i]; // 确定第一个元素作为"基准值" while (i < j) { // Stage1:从右向左扫描直到找到比基准值小的元素 while (i < j && arr[j].CompareTo(temp) >= 0) { j--; } // 将比基准值小的元素移动到基准值的左端 arr[i] = arr[j]; // Stage2:从左向右扫描直到找到比基准值大的元素 while (i<j && arr[i].CompareTo(temp) <= 0) { i++; } // 将比基准值大的元素移动到基准值的右端 arr[j] = arr[i]; } // 记录归位 arr[i] = temp; return i; }
在10000个随机数的数组中测试的性能结果如下图所示:
从上图可以看出,快速排序对于无序待排序数组的耗时只有15ms,比Shell排序还快了6ms,它的确是“快速”的。
总结:快速排序的平均时间复杂度为O(nlog2n),在平均时间下,快速排序时目前被认为最好的内部排序方法。但是,如果待排序记录的初始状态有序,则快速排序则会退化为冒泡排序,其时间复杂度为O(n2)。换句话说,待排序记录越无序,基准两侧记录数量越接近,排序速度越快;相反,待排序记录越有序,则排序速度越慢。
对于快速排序的改进一般集中在以下几个方面:
①当划分到较小的子序列时,通常可以使用插入排序替代快速排序;
②使用三平均分区法代替第一个元素作为基准值所出现的某些分区严重不均的极端情况;
③使用并行化处理排序;