O(n*logn)级别的算法之二(快速排序)的三种实现方法详解及其与归并排序的对比
快速排序被称为二十世纪算法界的一大杰作
一,单路快排
1.测试用例:
#ifndef INC_06_QUICK_SORT_DEAL_WITH_NEARLY_ORDERED_ARRAY_SORTTESTHELPER_H #define INC_06_QUICK_SORT_DEAL_WITH_NEARLY_ORDERED_ARRAY_SORTTESTHELPER_H #include <iostream> #include <algorithm> #include <string> #include <ctime> #include <cassert> using namespace std; namespace SortTestHelper { // 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR] int *generateRandomArray(int n, int range_l, int range_r) { int *arr = new int ; srand(time(NULL)); for (int i = 0; i < n; i++) arr[i] = rand() % (range_r - range_l + 1) + range_l; return arr; } // 生成一个近乎有序的数组 // 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据 // swapTimes定义了数组的无序程度 int *generateNearlyOrderedArray(int n, int swapTimes){ int *arr = new int ; for(int i = 0 ; i < n ; i ++ ) arr[i] = i; srand(time(NULL)); for( int i = 0 ; i < swapTimes ; i ++ ){ int posx = rand()%n; int posy = rand()%n; swap( arr[posx] , arr[posy] ); } return arr; } // 拷贝整型数组a中的所有元素到一个新的数组, 并返回新的数组 int *copyIntArray(int a[], int n){ int *arr = new int ; copy(a, a+n, arr); return arr; } // 打印arr数组的所有内容 template<typename T> void printArray(T arr[], int n) { for (int i = 0; i < n; i++) cout << arr[i] << " "; cout << endl; return; } // 判断arr数组是否有序 template<typename T> bool isSorted(T arr[], int n) { for (int i = 0; i < n - 1; i++) if (arr[i] > arr[i + 1]) return false; return true; } // 测试sort排序算法排序arr数组所得到结果的正确性和算法运行时间 template<typename T> void testSort(const string &sortName, void (*sort)(T[], int), T arr[], int n) { clock_t startTime = clock(); sort(arr, n); clock_t endTime = clock(); cout << sortName << " : " << double(endTime - startTime) / CLOCKS_PER_SEC << " s"<<endl; assert(isSorted(arr, n)); return; } }; #endif
2.归并排序:
#ifndef INC_06_QUICK_SORT_DEAL_WITH_NEARLY_ORDERED_ARRAY_MERGESORT_H #define INC_06_QUICK_SORT_DEAL_WITH_NEARLY_ORDERED_ARRAY_MERGESORT_H #include <iostream> #include <algorithm> #include "InsertionSort.h" using namespace std; // 将arr[l...mid]和arr[mid+1...r]两部分进行归并 template<typename T> void __merge(T arr[], int l, int mid, int r){ T aux[r-l+1]; //T *aux = new T[r-l+1]; for( int i = l ; i <= r; i ++ ) aux[i-l] = arr[i]; // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1 int i = l, j = mid+1; for( int k = l ; k <= r; k ++ ){ if( i > mid ){ // 如果左半部分元素已经全部处理完毕 arr[k] = aux[j-l]; j ++; } else if( j > r ){ // 如果右半部分元素已经全部处理完毕 arr[k] = aux[i-l]; i ++; } else if( aux[i-l] < aux[j-l] ) { // 左半部分所指元素 < 右半部分所指元素 arr[k] = aux[i-l]; i ++; } else{ // 左半部分所指元素 >= 右半部分所指元素 arr[k] = aux[j-l]; j ++; } } //delete[] aux; } // 使用优化的归并排序算法, 对arr[l...r]的范围进行排序 template<typename T> void __mergeSort(T arr[], int l, int r){ // 对于小规模数组, 使用插入排序 if( r - l <= 15 ){ insertionSort(arr, l, r); return; } int mid = (l+r)/2; __mergeSort(arr, l, mid); __mergeSort(arr, mid+1, r); // 对于arr[mid] <= arr[mid+1]的情况,不进行merge // 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失 if( arr[mid] > arr[mid+1] ) __merge(arr, l, mid, r); } template<typename T> void mergeSort(T arr[], int n){ __mergeSort( arr , 0 , n-1 ); } #endif
3.优化时要用的插入排序:
#ifndef INC_06_QUICK_SORT_DEAL_WITH_NEARLY_ORDERED_ARRAY_INSERTIONSORT_H #define INC_06_QUICK_SORT_DEAL_WITH_NEARLY_ORDERED_ARRAY_INSERTIONSORT_H #include <iostream> #include <algorithm> using namespace std; template<typename T> void insertionSort(T arr[], int n){ for( int i = 1 ; i < n ; i ++ ) { T e = arr[i]; int j; for (j = i; j > 0 && arr[j-1] > e; j--) arr[j] = arr[j-1]; arr[j] = e; } return; } // 对arr[l...r]范围的数组进行插入排序 template<typename T> void insertionSort(T arr[], int l, int r){ for( int i = l+1 ; i <= r ; i ++ ) { T e = arr[i]; int j; for (j = i; j > l && arr[j-1] > e; j--) arr[j] = arr[j-1]; arr[j] = e; } return; } #endif
4.单路快排实现:
#include <iostream> #include <algorithm> #include <ctime> #include "SortTestHelper.h" #include "MergeSort.h" #include "InsertionSort.h" using namespace std; // 对arr[l...r]部分进行partition操作 // 返回p, 使得arr[l...p-1] < arr[p]图解单路快排:; arr[p+1...r] > arr[p] template <typename T> int _partition(T arr[], int l, int r){ // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot swap( arr[l] , arr[rand()%(r-l+1)+l] ); T v = arr[l]; int j = l; for( int i = l + 1 ; i <= r ; i ++ ) if( arr[i] < v ){ j ++; swap( arr[j] , arr[i] ); } swap( arr[l] , arr[j]); return j; } // 对arr[l...r]部分进行快速排序 template <typename T> void _quickSort(T arr[], int l, int r){ // 对于小规模数组, 使用插入排序进行优化 if( r - l <= 15 ){ insertionSort(arr,l,r); return; } int p = _partition(arr, l, r); _quickSort(arr, l, p-1 ); _quickSort(arr, p+1, r); } template <typename T> void quickSort(T arr[], int n){ srand(time(NULL)); _quickSort(arr, 0, n-1); } // 比较Merge Sort和Quick Sort两种排序算法的性能效率 int main() { int n = 100000; // 测试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); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); delete[] arr1; delete[] arr2; cout<<endl; // 测试2 测试近乎有序的数组 // 加入了随机选择标定点的步骤后, 我们的快速排序可以轻松处理近乎有序的数组 // 但是对于近乎有序的数组, 其效率比优化后的归并排序要低, 但完全再容忍范围里 int swapTimes = 100; cout<<"Test for nearly ordered array, size = "<<n<<", swap time = "<<swapTimes<<endl; arr1 = SortTestHelper::generateNearlyOrderedArray(n,swapTimes); arr2 = SortTestHelper::copyIntArray(arr1, n); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); delete[] arr1; delete[] arr2; cout<<endl; // 测试3 测试存在包含大量相同元素的数组 // 但此时, 对于含有大量相同元素的数组, 我们的快速排序算法再次退化成了O(n^2)级别的算法 cout<<"Test for random array, size = "<<n<<", random range [0,10]"<<endl; arr1 = SortTestHelper::generateRandomArray(n,0,10); arr2 = SortTestHelper::copyIntArray(arr1, n); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); delete[] arr1; delete[] arr2; return 0; }
5.单路快排的测试结果:
可见单路快排在随机测试和近乎有序的情况下效率是完全可以接受的,但是在存在大量重复的元素中表现不是太好,下面我们进行一步一步的优化:
二,双路快排:
只需改一下主函数:
#include <iostream> #include <algorithm> #include <ctime> #include "SortTestHelper.h" #include "MergeSort.h" #include "InsertionSort.h" using namespace std; // 对arr[l...r]部分进行partition操作 // 返回p, 使得arr[l...p-1] < arr[p]图解双路快排:; arr[p+1...r] > arr[p] template <typename T> int _partition(T arr[], int l, int r){ // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot swap( arr[l] , arr[rand()%(r-l+1)+l] ); T v = arr[l]; int j = l; for( int i = l + 1 ; i <= r ; i ++ ) if( arr[i] < v ){ j ++; swap( arr[j] , arr[i] ); } swap( arr[l] , arr[j]); return j; } // 双路快速排序的partition // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] template <typename T> int _partition2(T arr[], int l, int r){ // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot swap( arr[l] , arr[rand()%(r-l+1)+l] ); T v = arr[l]; // arr[l+1...i) <= v; arr(j...r] >= v int i = l+1, j = r; while( true ){ // 注意这里的边界, arr[i] < v, 不能是arr[i] <= v while( i <= r && arr[i] < v ) i ++; // 注意这里的边界, arr[j] > v, 不能是arr[j] >= v while( j >= l+1 && arr[j] > v ) j --; if( i > j ) break; swap( arr[i] , arr[j] ); i ++; j --; } swap( arr[l] , arr[j]); return j; } // 对arr[l...r]部分进行快速排序 template <typename T> void _quickSort(T arr[], int l, int r){ // 对于小规模数组, 使用插入排序进行优化 if( r - l <= 15 ){ insertionSort(arr,l,r); return; } // 调用双路快速排序的partition int p = _partition2(arr, l, r); _quickSort(arr, l, p-1 ); _quickSort(arr, p+1, r); } template <typename T> void quickSort(T arr[], int n){ srand(time(NULL)); _quickSort(arr, 0, n-1); } // 比较Merge Sort和双路快速排序两种排序算法的性能效率 int main() { int n = 100000; // 测试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); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); delete[] arr1; delete[] arr2; cout<<endl; // 测试2 测试近乎有序的数组 // 双路快速排序算法也可以轻松处理近乎有序的数组 int swapTimes = 100; cout<<"Test for nearly ordered array, size = "<<n<<", swap time = "<<swapTimes<<endl; arr1 = SortTestHelper::generateNearlyOrderedArray(n,swapTimes); arr2 = SortTestHelper::copyIntArray(arr1, n); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); delete[] arr1; delete[] arr2; 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); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); delete[] arr1; delete[] arr2; return 0; }
双路快排测试结果:
可见优化后的双路快排在大量重复元素的数组测试中速度提升了近乎200倍,接下来让我们在来看看最棒的三路快排:
三,三路快排:
1.我们将刚才的双路快排写到一个头文件中以便等下测试:
#ifndef INC_07_QUICK_SORT_THREE_WAYS_QUICKSORT_H #define INC_07_QUICK_SORT_THREE_WAYS_QUICKSORT_H #include <iostream> #include <ctime> #include <algorithm> #include "InsertionSort.h" using namespace std; // 对arr[l...r]部分进行partition操作 // 返回p, 使得arr[l...p-1] < arr[p]2.三路快排实现:; arr[p+1...r] > arr[p] template <typename T> int _partition(T arr[], int l, int r){ // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot swap( arr[l] , arr[rand()%(r-l+1)+l] ); T v = arr[l]; int j = l; for( int i = l + 1 ; i <= r ; i ++ ) if( arr[i] < v ){ j ++; swap( arr[j] , arr[i] ); } swap( arr[l] , arr[j]); return j; } // 双路快速排序的partition // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] template <typename T> int _partition2(T arr[], int l, int r){ // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot swap( arr[l] , arr[rand()%(r-l+1)+l] ); T v = arr[l]; int i = l+1, j = r; while( true ){ // 注意这里的边界, arr[i] < v, 不能是arr[i] <= v while( i <= r && arr[i] < v ) i ++; // 注意这里的边界, arr[j] > v, 不能是arr[j] >= v while( j >= l+1 && arr[j] > v ) j --; if( i > j ) break; swap( arr[i] , arr[j] ); i ++; j --; } swap( arr[l] , arr[j]); return j; } // 对arr[l...r]部分进行快速排序 template <typename T> void _quickSort(T arr[], int l, int r){ // 对于小规模数组, 使用插入排序进行优化 if( r - l <= 15 ){ insertionSort(arr,l,r); return; } // 调用双路快速排序的partition int p = _partition2(arr, l, r); _quickSort(arr, l, p-1 ); _quickSort(arr, p+1, r); } template <typename T> void quickSort(T arr[], int n){ srand(time(NULL)); _quickSort(arr, 0, n-1); } #endif
#include <iostream> #include <algorithm> #include <ctime> #include "SortTestHelper.h" #include "MergeSort.h" #include "InsertionSort.h" #include "QuickSort.h" using namespace std; // 递归的三路快速排序算法 template <typename T> void __quickSort3Ways(T arr[], int l, int r){ // 对于小规模数组, 使用插入排序进行优化 if( r - l <= 15 ){ insertionSort(arr,l,r); return; } // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot swap( arr[l], arr[rand()%(r-l+1)+l ] ); T v = arr[l]; int lt = l; // arr[l+1...lt] < v int gt = r + 1; // arr[gt...r] > v int i = l+1; // arr[lt+1...i) == v while( i < gt ){ if( arr[i] < v ){ swap( arr[i], arr[lt+1]); i ++; lt ++; } else if( arr[i] > v ){ swap( arr[i], arr[gt-1]); gt --; } else{ // arr[i] == v i ++; } } swap( arr[l] , arr[lt] ); __quickSort3Ways(arr, l, lt-1); __quickSort3Ways(arr, gt, r); } template <typename T> void quickSort3Ways(T arr[], int n){ srand(time(NULL)); __quickSort3Ways( arr, 0, n-1); } int main() { int n = 100000; // 测试1 一般性测试 cout<<"一般随机测试:"<<endl; 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); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr3, n); delete[] arr1; delete[] arr2; delete[] arr3; cout<<endl; // 测试2 测试近乎有序的数组 cout<<"测试近乎有序的数组"<<endl; int swapTimes = 100; 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); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr3, n); delete[] arr1; delete[] arr2; delete[] arr3; cout<<endl; // 测试3 测试存在包含大量相同元素的数组 cout<<"测试存在包含大量相同元素的数组"<<endl; 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); SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n); SortTestHelper::testSort("Quick Sort", quickSort, arr2, n); SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr3, n); delete[] arr1; delete[] arr2; delete[] arr3; return 0; }
图解三路快排:
三路快排测试结果:
可见性能又进一步提升了:
比较Merge Sort和双路快速排序和三路快排三种排序算法的性能效率
对于包含有大量重复数据的数组, 三路快排有巨大的优势
对于一般性的随机数组和近乎有序的数组, 三路快排的效率虽然不是最优的, 但是在可以接受的范围里
因此, 在一些语言中, 三路快排是默认的语言库函数中使用的排序算法。比如Java:)
- js实现常见的三种排序方法(冒泡排序、快速排序、归并排序)
- 白话经典算法系列之二 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现
- 几种常见排序算法之Java实现(插入排序、希尔排序、冒泡排序、快速排序、选择排序、归并排序)
- 【白话经典算法系列之二】 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现
- 【转载】白话经典算法系列之二 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现
- C语言实现排序算法之归并排序详解
- 白话经典算法系列之二 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现
- 3种排序方法的对比(冒泡排序,快速排序,归并排序)
- 白话经典算法系列之二 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现
- 白话经典算法系列之二 直接插入排序的三种实现 .
- 三种简单排序算法及其对比
- 快速排序的三种实现方法体会
- 经典快速排序,linux纯C实现。注意swap方法,并且这个算法还需再敲写15篇。