快速排序(quick sort)
本想先简单的实现下快速排序,没想到看到一个博客后发现了新大陆,一个快排竟然有这么多优化方法,链接在此:https://blog.csdn.net/qq_38289815/article/details/82718428。
在简单的消化快速排序的知识后,在此记录。
快速排序是冒泡排序的进阶排序算法,它的平均时间复杂度为O(nlogn),在最差的情况下也就是在基准数在数组的一头,然后边上的数都比基准数大的时候,它的排序就变成了冒泡排序,时间复杂度变为O(n^2) 。
快速排序的思想是:
先从待排序数组中选定一个基准数,然后用两个指针i跟j分别指向数组的最左边和最右边,先让指针j从右边向左移动,在不与i重合的情况下发现比基准数小的元素时,就将指针j当前所指的元素跟基准数换位,此时基准数换到了j所指的位置,然后让L往数组右边移动,在不与j重合的情况下找到比基准数大的数时停下,将基准数与i所指位置交换,就这样直到i跟j重合,此时的基准数所在位置也在i,j所指位置,你会发现此时在基准位置左边的数都小于基准数,右边的都大于基准数。具体过程如下图1所示:
图1
快排的分治思想可以用函数的递归来实现,为了防止递归深度过大导致栈内存爆炸,安全的做法是采用尾递归方法,我之前先去了解了尾递归的知识。我的理解是:只要回归过程中没有任何操作,当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
我的尾递归快速排序代码如下:
头文件Sort.h:
1 //自己编写的各种排序算法的头文件。 2 #ifndef _SORT_H 3 #define _SORT_H 4 //冒泡排序 5 class bubble_sort{ 6 private: 7 int *Array,Length; 8 public: 9 bubble_sort(int *Array1,int Length); 10 int *sort_bubble(); 11 12 }; 13 //归并排序 14 class merge_sort{ 15 public: 16 static void sort_merge(int Array1[], int Array2[], int Left, int Rightend, int Right,int recursion); 17 static void Merge(int Array1[], int Array2[], int Left, int Rightend);//递归方法 18 static void Merge_pass(int Array1[], int Array2[], int ordered_len,int Length); 19 static void Merge1(int Array1[], int Array2[], int Length);//非递归方法 20 }; 21 class quick_sort{ 22 public: 23 static void sort_quick(int Array1[],int Left,int right); 24 25 }; 26 #endif
quick_sort.cpp文件:
1 #include "Sort.h" 2 3 void quick_sort::sort_quick(int Array1[], int Left, int Right) 4 { 5 if (Left<Right) 6 { 7 int T = Array1[Left], Leftbegin = Left, Rightend = Right,temp; 8 while (Left<Right) 9 { 10 while (Array1[Right] >= T &&Right>Left)//左移右指针。 11 Right--; 12 //交换右边指针所指数与基准数。 13 temp = Array1[Left]; 14 Array1[Left] = Array1[Right]; 15 Array1[Right] =temp; 16 while (Array1[Left] <T &&Right>Left)//右移左指针 17 Left++; 18 //交换左边指针所指数与基准数。 19 temp = Array1[Left]; 20 Array1[Left] = Array1[Right]; 21 Array1[Right] = temp; 22 } 23 //尾递归。 24 quick_sort::sort_quick(Array1, Leftbegin, Left-1); 25 quick_sort::sort_quick(Array1, Left + 1, Rightend); 26 } 27 }
main.cpp:
1 #include <iostream> 2 #include "Sort.h" 3 using namespace std; 4 void main() 5 { 6 int Array[10] = { 30, 2, 1111, 4,8,10,100,33,40,11 }; 7 int N = sizeof(Array) / sizeof(int) ; 8 quick_sort::sort_quick(Array, 0,N-1); 9 10 for (int i = 0; i < sizeof(Array) / sizeof(int); i++) 11 { 12 cout << Array[i] << endl; 13 } 14 system("pause"); 15 }
快速排序的效率受基准点的选取的影响最大,当基准点被选为数组第一个数或者最后一个数的时候,若待排序的是一个有序数组,也就是说,每次遍历一个长度为n的数组之后通过基准点分割出来的两个数组的长度分别为0和n-1,那么快速排序就会变成冒泡排序,它的时间复杂度会变成O(n^2),又或者当你不是选择数组第一个数或者最后一个数最为基准点而是随机选择数组中一个位置为基准点,而每次这个基准点刚好是这组数列中最大或者最小的数的时候,情况又会变成上面说到的,分割出两端长度极不平衡的两端序列,时间复杂度升至O(n^2)。
最理想的状态就是每次通过基准点分割出来的两个数组的长度皆为n/2。所以随机选取基准点是一个改进的办法,又或者是选取数组的中间位置的点,最保险的方法是选取第一个点的数和中间位置的数和最后一个数中的中间值的位置。
在最上面所给的链接的博客中,有写到快速排序的很多优化方式,实际上在工程上使用排序时,往往是多个排序方式联合使用,就如当子序列长度小于20个时可以换成插入排序对子序列进行排序。
快速排序能解决什么问题呢?
之前在面试时难到过我的一个问题:在一个拥有巨大数据量的数组中找到数值排前100位的数。
这个问题的其中一种优化解法就用到了快速排序的分治算法。
我现在的解答是:
先随机选定数组中一个位置的数选为基准数,经过一次快速排序后(不用继续递归),我们能得到该位置的数的大小在该数组中的排名k,
当k排名在99之前的话,我们就在数组位置[k+1~数组.length-1]的区间选定一个位置的数作为基准点继续递归;
当k排名在99名之后的话,我们就在数组位置[0~k-1]的区间选定一个位置的数作为基准点继续递归;
直到找到k=99的数。
这么做跟快速排序的稳定性一样,是不稳定的。因此还有别的解法,这里就不多赘述了。
仍然希望读者们发现不足之处能给予指正,谢谢!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步