文章目录
1、梳理背景
最近在查阅关于快速排序算法的相关实现,发现关于算法原理、源码实现出现不同的变种版本,个人觉得有些版本不好理解,有些版本更易于理解。参考了各种不同的版本,通过自己实践,最终将此算法的原理、及源码实现进行了全新的梳理,更易于掌握。
2、算法原理
给定一个数字类的数组(arr[]),选定一个基准数,一般以数组第1个数作为基准数,在数组的最左侧(位置为0,设置为left)及数组的最右侧(java,如数组长度为6,位置为5,设置为right)。
第一步:先从右往左查找
开始执行查找,必须【先从右侧往左】进行查找(即 right- -),直到找到第1个小于基准数的数,然后记下right的位置,则从右往左查找结束;如果直到 right- -=0 都还没找到小于基准数的数,则从右往左查找结束。
第二步:在从左往右查找
继续接着上一步操作,先判断当前left(当前是0)是否小于当前right的值。
如果,left < right 说明左右位置还未相遇,则开始继续【从左往右】进行查找(即left+ +),直到找到第1个大于基准数的数,然后记下left的位置,则从左往右查找结束;否则(即left>=right),说明左右已经相遇,则从左往右查找结束。
第三步:交换值
继续接着上一步操作,如果,左和右都找到了比基准数大和小的数,且当前left的位置小于当前right的位置(left < right),则交换arr[left]和arr[rght]的值;
第四步:左右相遇
当左右相遇时,结束查找,并将相遇的数与基准数交换,返回交换后基准值的位置(k)。到此,第一轮排序完成。
第五步:继续递归调用重复上面的123步骤
当第一轮执行完成后,会返回上一次基准值的位置(k),此时我们发现,位于k左边的数都是小于基准值的数(左子数组),位于k右边的数都是大于基准值的数(右子数组)。但是,左子数组、右子数组依然是乱序的,所以,我们可以对左右子数组进行分别排序。这时候我们就可以重复前面的12345步骤(递归调用)对子数组进行排序,直到左右子数组全部排序完成。
关键核心点:
- 核心思想:就是将数组以基准点开始,找到分割点k,将数组一分为二,再针对子数组,继续选定子基准点开始,查找子数组的分割点k,再将子数组一分为二,如此重复,直到全部排序完成。
- 重合点查找情况1,一定是左边递增序列值>=右边递减序列值代表左右相遇了,本次右左查找完成,程序继续往下,还需执行一次动作,即将基准值与相遇位置的值进行交换,目的是为了将大于基准数和小于基准数的数,分隔到基准数的两边。
- 重合点查找情况2,我们选定位置0的值为基准数,如果从右往左,发现right- - = 0,代表都没找到一个小于基准值的数,这时候停下来,开始从左往右查找,left从0开始递增时,发现left=right=0,这时候左右序列值在0处相遇了,由于没有找到合适的值,所以直接将基准数(即位置0的值)和位置0对应的数交换,自己与自己交换一次,本轮查找执行完成。
- 查找顺序,一定是先从右往左,后从左往右。
3、时间复杂度
最优情况下时间复杂度:O( nlogn );
最差情况下时间复杂度:O( n^2 );
4、空间复杂度
最优的情况下空间复杂度为:O(logn) ;每一次都平分数组的情况
最差的情况下空间复杂度为:O( n ) ;退化为冒泡排序的情况
5、源码实现
public static void main(String[] args) {
int[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
showResult(arr);
mqs(arr, 0, arr.length - 1);
showResult(arr);
}
private static void mqs(int[] arr, int low, int high) {
int pivot;
if (low < high) {
//获取上一次基准数的位置
pivot = myQuickSort(arr, low, high);
//从上一次基准数的左右两边继续交换
mqs(arr, low, pivot - 1);
mqs(arr, pivot + 1, high);
}
}
private static int myQuickSort(int[] arr, int startIndex, int endIndex) {
//查找基准值 选取第一个数
int pVal = arr[startIndex];
int left = startIndex;
int right = endIndex;
//当i与j相遇时循环暂停
while (left != right) {
//前置条件,左边必须小于右边,代表两端已经相遇,从右往左扫描完成
//先从右往左扫描,如果找到第一个小于基准的数,直接跳出循环
while (left < right && arr[right] > pVal) {
right--;
}
//前置条件,左边必须小于右边,否则就代表两端已经相遇,从左往右扫描完成
//接着从左往右扫描,如果找到第一个大于基准的数,直接跳出循环
while (left < right && arr[left] <= pVal) {
left++;
}
//从右边和左边都找到了对应的,小于基准值的数和大于基准值的数,且还左边和右边的位置还未相遇,则直接交换值
if (left < right) {
changePos(arr, left, right);
System.out.println("========特殊开始=========");
showResult(arr);
System.out.println("========特殊结束=========");
}
}
//第一轮完成,让startIndex和right重合的位置的值和基准值交换,返回基准的位置
changePos(arr, startIndex, right);
showResult(arr);
return left;
}
private static void showResult(int[] arr) {
System.out.println("------------------");
for (int i = 0; i < arra.length; i++) {
System.out.println(arr[i]);
}
}
private static void changePos(int[] arr, int i, int j) {
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
输出结果:
...
------------------
1
2
3
4
5
6
7
8
9
10
6、图片演示
参考链接:
原创不易,求个关注。
微信公众号:一粒尘埃的漫旅
里面有很多想对大家说的话,就像和朋友聊聊天。
写代码,做设计,聊生活,聊工作,聊职场。
我见到的世界是什么样子的?
搜索关注我吧。
公众号与博客的内容不同。