【算法】排序01——从分治算法的角度理解快速排序(含代码实现)

快速排序的时间复杂度为 O[nlog (n)],空间复杂度是 O[log (n)] ,这种算法应用比较普遍(面试也爱考),很是适合在数据量较多且关键字随机分布的状况下使用。java

记得博主第一次学习冒泡排序时以为,这个冒泡排序很easy啊,而后就去看冒泡排序改进版的代码(也就是快速排序啦),而后。。。。面试

 啊这 。。。算法

相信应该也有一些小伙伴和我过相似的经历吧。即便能读懂代码,却有难以理解其流程,面试手撕时也难以快速应对。数组

不事后来,当我接触到分治算法后,再回过头来这个快速排序的方法。。。。学习

就这?之后快排我不用提早准备,随时手撕!测试

 好啦,废话很少说了,进入正题。ui


 

先说一说什么是分治算法:spa

就如字面意思,咱们须要把一个大的问题分解成多个形式相同的小问题解决,而且这些小问题的解的合并就是大问题的解。指针

这可能理解起来仍是抽象的,咱们就以排序举例子:code

假设咱们的数组有这样九个数字: [ 5 8 9 6 7 4 1 2 3 ] 

如今咱们从数组中随便取一个数字(就去数组中的第一个数字吧),以它为阈值,全部小于他数的放其左边,并在逻辑上视为一个新的数组(就叫小”数组“吧)。全部大于他的数放其右边,一样在逻辑上视为一个新的数组(就叫大”数组“吧),而后咱们再对新出现的大数组和小数组进行一样的操做直到新数组的大小小于2时中止:

以第一个数字5为阈值进行拆分操做,分红 [ 小数组 ] 阈值 [ 大数组 ] ,以下:

[ 4 1 2 3 ]  5  [ 8 9 6 7 ]         (在快速排序中小”数组“其实会是[ 3 2 1 4 ],由于快排会从原数组首尾的两个指针向中间交替前进分大小,但这对咱们这个例子并没有影响)

[ 1 2 3 ]  4 [ ]   5   [ 6 7 ] 8 [ 9 ]     黄色部分是对[4 1 2 3]的拆分操做,绿色部分是对 [8 9 6 7]的拆分操做,红色部分要么是当前操做的阈值要么是长度不足2的数组,将不会参与后面的拆分操做

[ ] 1 [ 2 3 ] 4 [ ] 5  6 [ 7 ] 8 [ 9 ]     粉色部分是对[ 1 2 3 ]的拆分操做 ,淡蓝色部分是对[ 6 7 ] 的拆分操做, 红色部分要么是当前操做的阈值要么是长度不足2的数组,将不会参与后面的拆分操做

[ ] 1 2 [ 3 ] 4 [ ] 5 6 [ 7 ] 8 [ 9 ]      深蓝色部分是对‘[ 2 3 ]的拆分操做,红色部分要么是当前拆分操做的阈值要么是长度不足2的数组,将不会参与后面的拆分操做

如今你看,这个数组是否是有序了。

 

 

 

这就有点像创建一个二叉搜索树同样,把一个数组分红一个阀值(父节点)、一个小数组(左子树集合)和一个大数组(右子树集合),而后对两个新的数组(左、右子树)递归该操做。

固然,快排和这个流程仍是有一点小区别的,当咱们把一个“数组”拆分红两个新“数组”时,上面的演示流程为了方便你们理解,不凭空增长复杂性,采用的是从左向右遍历原数组的每一个元素。

如 [ 5 8 9 6 7 4 1 2 3 ]  操做后的小数组顺序是 [ 4 1 2 3 ]。但在快排中,对原数组的遍历不是这样的,它是使用两个指针从原数组首尾开始向中间前进进行遍历的(小伙伴能够在下面的代码里体会),因此我才在上面的小括号里说快排中的小数组顺序是[ 3 2 1 4 ]。(即尾指针向中间前进,遇到小于阈值5的数字的顺序 3 2 1 4 )。

最后,你们从分治的思想来看看下面的快排代码,是否是就不以为更好理解了呢?

 1 import java.util.Arrays;  2 
 3 public class QuickSort {  4     public static void sort(int[] array,int begin_edge,int end_edge){  5         //左右边界下标重合时,就表示逻辑上的新数组大小不足2了,  6         // 故再也不须要二分红两个新“数组”了
 7         if(begin_edge>=end_edge){return;}  8         //获取新“数组”的首尾指针
 9         int l_pointer = begin_edge; 10         int r_pointer = end_edge; 11         int threshold = array[l_pointer]; 12         //循环结束时,全部l_pointer左边的元素会小于threshold,右边的会大于threshold(有点二叉搜索树的意思2333)
13         while (l_pointer<r_pointer){//跳出循环时完成对当前“数组”的二分 14             //尾指针向前遍历,遇到小于阈值的数就跳出循环,把它放进小“数组”
15             while (l_pointer<r_pointer && array[r_pointer]>=threshold){ 16                 r_pointer--; 17  } 18             array[l_pointer] = array[r_pointer]; 19             //首指针向后遍历,遇到大于阈值的数就跳出循环,把它放进大“数组”
20             while (l_pointer<r_pointer && array[l_pointer]<=threshold) { 21                 l_pointer++; 22  } 23             array[r_pointer] = array[l_pointer]; 24  } 25         //把阈值插到两个“数组”的中间以区分两个“数组”的边界(此时l_pointer等于r_pointer)
26         array[l_pointer] = threshold; 27         //递归调用。分治思想,化大问题为形式相同的小问题
28         sort(array,begin_edge,l_pointer-1); 29         sort(array,l_pointer+1,end_edge); 30  } 31 
32     public static void main(String[] args) { 33         int array[] = { 4, 2, 5, 1, 7, 3, 5, 9, 8,1 }; 34         sort(array,0,array.length-1); 35  System.out.println(Arrays.toString(array)); 36  } 37 }

测试结果:

最后的最后,若是小伙伴以为这篇博文对你有帮助的话,就长按👍。。。。啊,呸,就点个推荐吧

相关文章
相关标签/搜索