排序算法学习——快速排序
2018-02-26 19:57
281 查看
前言
上一篇我们已经实现了一个O(NlogN)级别的归并排序,这一篇的话,我们就来实现另一个O(NlogN)级别的排序算法,鼎鼎大名的快速排序。快速排序
原理
我们先在数组中找到一个数,然后以这个数为基准,分成两部分,一部分小于这个数,另一部分大于这个数。完成之后,这个数就位于了正确的位置,接下来采用递归的方法对左右两边的两个数组继续进行快速排序。代码实现
代码的图片解释:/** 对arrp[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) { //取第一个元素为标准 T v = arr[L]; //arr[L+1...j] < v && arr[j+1...i) > v int j = L; for(int i = L + 1; i <= R; i++) { if(arr[i] < v) { swap(arr[j+1], arr[i]); j++; } } //最后将标准元素放置到中间 swap(arr[L], arr[j]); return j; } /** 对arr[L...R]部分进行快速排序 */ template <typename T> void __quickSort(T arr[], int L, int R) { //跳出条件 if(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) { __quickSort(arr, 0, n-1); }
优化
优化1.0
我们可以看到我们是以第一个数字作为基准的,这样带来的坏处是:当数组几乎有有序的情况下,快速排序的时间复杂度将会下降到O(n^2)。其实快速排序和归并排序有一点是相似的,他们都是使用递归将过程不断地分层,不同点在于归并排序是很稳定地将数组进行二分,所以最后的层数应该是:log2N
而快速排序的切割方法并不稳定,所以导致了近乎有序的情况下效率较低。
有什么办法解决这个问题呢?答案当然是有的。如果我们将参考的数字从第一个改成完全随机的,那么是不是就解决有序的情况下效率低的问题,当然你说随机之后还是有可能,这是当然的,但是完全随机之后在有大量数据的情况下是几乎不可能有序的。
优化1.0修改代码
首先设定随机种子,修改quickSort函数:template <typename T> void quickSort(T arr[], int n) { //改进1.0,随机选择标准而不是第一个,解决近乎有序情况下的问题 srand(time(NULL)); __quickSort(arr, 0, n-1); }
接着修改随机取的数字,只需要添加一行代码,随机取一个数和第一个数字进行交换:
/** 对arrp[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) { //改进1.0,随机选择一个数 swap(arr[L], arr[rand()%(R-L+1) + L]); //取第一个元素为标准 T v = arr[L]; //arr[L+1...j] < v && arr[j+1...i) > v int j = L; for(int i = L + 1; i <= R; i++) { if(arr[i] < v) { swap(arr[j+1], arr[i]); j++; } } //最后将标准元素放置到中间 swap(arr[L], arr[j]); return j; }
优化2.0
递归的跳出条件同样可以做一些修改,比如在数组较小的时候可以进行插入排序。这里我就不重复解释了,有兴趣可以看一下我之前归并排序是如何修改的。排序算法学习——归并排序优化3.0
刚刚解决了近乎有序的情况,但是还有一种情况,那就是当存在大量相同大小的数据时,会导致我们的排序层数不平衡,我们的算法就又会退化到O(N^2)级别的算法。为了解决这个问题我们提出一种新的partition方式,从数组的两端同时开始扫描,左边扫描到大于v停止,右边扫描到小于v停止,两者进行一次交换,然后继续。因为我们在相等时仍然选择了交换,因此不会产生一端过长的情况。
原理演示如图:
代码修改如下:
/** 对arrp[L...R]部分进行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) { //改进1.0,随机选择一个数 swap(arr[L], arr[rand()%(R-L+1) + L]); //取第一个元素为标准 T v = arr[L]; //arr[L+1...i) < v && arr(j...r] > v i,j初始值保证一开始两个集合都不存在 int i = L + 1, j = R; while(true) { //扫描 while(i <= R && arr[i] < v) i++; while(j >= L + 1 && arr[j] > v) j--; //跳出条件 if(i > j) break; //交换 swap(arr[i], arr[j]); i++; j--; } //最后将标准元素放置到中间 //最后完成j位于<=v的最后一个,i位于>=v的第一个 swap(arr[L], arr[j]); return j; } /** 对arr[L...R]部分进行快速排序 */ template <typename T> void __quickSort2(T arr[], int L, int R) { //跳出条件,可以改进 if(L >= R) return; int p = __partition2(arr, L, R); __quickSort2(arr, L, p-1); __quickSort2(arr, p+1, R); } template <typename T> void quickSort2(T arr[], int n) { //改进1.0,随机选择标准而不是第一个,解决近乎有序情况下的问题 srand(time(NULL)); __quickSort2(arr, 0, n-1); }
三路快排
原理
其实这可以说是优化4.0,因为第二种快速排序方法优化3.0已经很完善了。但是三路快排还是值得单独拿出来讲一下的,因为它在处理具有大量相同大小的数据时更加迅速。同时,比如一些语言(比如Java)的内置排序算法实现也是三路快排。三路快排,哪三路比较快??当然是上路(小于v),中路(等于v),下路(大于v)一起才比较快啦。
我们最初的快速排序其实讲道理是两条路,一条小于v,另一条其实是大于等于 v,或者等号的位置换一下。这样的问题在于我们经常会重复处理相同大小的数据,这样降低了整个算法的效率。三路快排将等于v的数据单独拿出来,这样我们只要处理大于和小于v这两种数据了。
这就是中间状态,索引i不断向后推进,同时维护三个索引lt、gt、i:
最后状态,进行交换之后完成排序:
代码实现
/** 三路快排处理arr[L...R] 将arr[L...R]分为<v; ==v; >v三部分 之后递归对<v ; >v两部分继续进行三路快速排序 */ template <typename T> void __quickSort3Ways(T arr[], int L, int R) { //跳出条件 if(R - L <= 15) { insertionSort(arr, L, R); return; } //partition //随机取标准 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...i) == v while(i < gt) { if(arr[i] < v) { swap(arr[i], arr[lt+1]); lt++; i++; } 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); }
当然优化是无止境,这里就先介绍到这啦!!欢迎交流。
图片引用百度图片
代码实现参照liuyubobobo慕课网教程
相关文章推荐
- 排序算法学习之快速排序
- (三)几种排序算法的学习总结(快速排序)
- 记录自已学习之排序算法(快速排序)
- 经典排序算法学习笔记二——快速排序
- 排序算法学习- 快速排序
- 算法学习-排序算法-快速排序
- 算法学习笔记:排序算法整理
- 算法导论学习笔记-第七章-快速排序
- 排序算法之快速排序
- 常见排序算法学习及C#代码实现
- 【排序算法】排序算法之快速排序
- 排序算法-快速排序和堆排序
- 排序算法——快速排序的图解、代码实现以及时间复杂度分析
- 经典算法学习——快速排序
- 排序算法-快速排序(三种实现方案)
- 【学习笔记】C#中HashTable和快速排序的用法,从单词频率统计小程序写起
- 排序算法(一)--快速排序
- 排序算法之快速排序
- [直观学习排序算法] 视觉直观感受若干常用排序算法
- 排序算法 之 快速排序