快速排序(Quicksort),最早由东尼·霍尔基于分治思想提出的排序算法,现在各种语言中自带的排序库很多使用的都是快速排序。 快速排序的基本思想:通过一次排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
最基础的算法步骤:使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
动态图演示
算法优化思路
对于小规模数组进行插入排序优化
在数据近乎有序时,递归树的平衡度差会造成算法复杂度退化成 O ( n 2 ) O_{(n2)} O(n2),利用随机选取元素进行排序的方法优化
重新排序数列时相同的数可以到任一边,这种操作会使得算法遇到大量重复元素的排序时需要重复操作,优化:双路快速排序&三路快速排序
双路快速排序中: while( j >= l+1 && arr[j] > v ) while( i <= r && arr[i] < v )不取等号的原因分析: a. 对于arr[i]<v和arr[j]>v的方式,第一次partition得到的分点是数组中间; b. 对于arr[i]<=v和arr[j]>=v的方式,第一次partition得到的分点是数组的倒数第二个。 这是因为对于连续出现相等的情况,a方式会交换i和j的值;而b方式则会将连续出现的这些值归为其中一方,使得两棵子树不平衡
测试代码 #include <iostream> #include "SortTestHelper.h" #include "InsertionSort.h" #include "MergeSort.h" #include "QuickSort.h" using namespace std; int main(){ int n = 50000; // 测试1 测试随机数组 cout<<"Test for random array, size = "<<n<<", random range [0, "<<n<<"]"<<endl; int* arr1 = SortTestHelper::generateRandomArray(n,0,n); int* arr2 = SortTestHelper::copyIntArray(arr1, n); int* arr3 = SortTestHelper::copyIntArray(arr1, n); int* arr4 = SortTestHelper::copyIntArray(arr1, n); int* arr5 = SortTestHelper::copyIntArray(arr1, n); int* arr6 = SortTestHelper::copyIntArray(arr1, n); SortTestHelper::testSort("Insertion Sort", insertionSort, arr1, n); SortTestHelper::testSort("Merge Sort", mergeSort, arr2, n); SortTestHelper::testSort("Merge Sort 2", mergeSort2, arr3, n); SortTestHelper::testSort("Quick Sort", quickSort, arr4, n); SortTestHelper::testSort("Quick Sort 2 Ways", quickSort2Ways, arr5, n); SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr6, n); delete[] arr1; delete[] arr2; delete[] arr3; delete[] arr4; delete[] arr5; delete[] arr6; cout<<endl; // 测试2 测试近乎有序的数组 int swapTimes = 10; assert( swapTimes >= 0 ); cout<<"Test for nearly ordered array, size = "<<n<<", swap time = "<<swapTimes<<endl; arr1 = SortTestHelper::generateNearlyOrderedArray(n,swapTimes); arr2 = SortTestHelper::copyIntArray(arr1, n); arr3 = SortTestHelper::copyIntArray(arr1, n); arr4 = SortTestHelper::copyIntArray(arr1,n); arr5 = SortTestHelper::copyIntArray(arr1,n); arr6 = SortTestHelper::copyIntArray(arr1,n); SortTestHelper::testSort("Insertion Sort", insertionSort, arr1, n); SortTestHelper::testSort("Merge Sort", mergeSort, arr2, n); SortTestHelper::testSort("Merge Sort 2", mergeSort2, arr3, n); SortTestHelper::testSort("Quick Sort", quickSort, arr4, n); SortTestHelper::testSort("Quick Sort 2 Ways", quickSort2Ways, arr5, n); SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr6, n); delete[] arr1; delete[] arr2; delete[] arr3; delete[] arr4; delete[] arr5; delete[] arr6; cout<<endl; // 测试3 测试存在包含大量相同元素的数组 cout<<"Test for random array, size = "<<n<<", random range [0,10]"<<endl; arr1 = SortTestHelper::generateRandomArray(n,0,10); arr2 = SortTestHelper::copyIntArray(arr1, n); arr3 = SortTestHelper::copyIntArray(arr1, n); arr4 = SortTestHelper::copyIntArray(arr1,n); arr5 = SortTestHelper::copyIntArray(arr1,n); arr6 = SortTestHelper::copyIntArray(arr1,n); SortTestHelper::testSort("Insertion Sort", insertionSort, arr1, n); SortTestHelper::testSort("Merge Sort", mergeSort, arr2, n); SortTestHelper::testSort("Merge Sort 2", mergeSort2, arr3, n); // 在包含大量重复元素的情况下, QuickSort会退化成O(n^2)算法, 在这里不做执行 //SortTestHelper::testSort("Quick Sort", quickSort, arr4, n); SortTestHelper::testSort("Quick Sort 2 Ways", quickSort2Ways, arr5, n); SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr6, n); delete[] arr1; delete[] arr2; delete[] arr3; delete[] arr4; delete[] arr5; delete[] arr6; return 0; } 实验结果快速排序是一种原地排序,只需要一个很小的栈作为辅助空间,空间复杂度为 O ( l o g 2 n ) O_(log2n) O(log2n),所以适合在数据集比较大的时候使用。 时间复杂度比较复杂,最好的情况是 O ( n ) O_{(n)} O(n),最差的情况是 O ( n 2 ) O_{(n2)} O(n2),所以平时说的 O ( n l o g n ) O_(nlogn) O(nlogn),为其平均时间复杂度。对于随机数组、近乎有序的数组和大量重复元素的数组,三种QuickSort的性能各有不同。
