图解快速排序算法

Posted Java软件编程之家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图解快速排序算法相关的知识,希望对你有一定的参考价值。



图解快速排序算法

 


 

快速排序定义


一般来说,算法就像数学公式,前人经过不断优化和验证得到有规律性的公式留给后人使用,当然也会交给后人验证的思路。那么快速排序算法就是这样,它有基本固定的定义如下:

 

tip:如果一时不理解定义可以直接跳过,直接看图解然后再回来看定义,你就会豁然开朗。另外,文章描述时下标从1开始,实际代码数组大多都是从0开始,所以在代码中需要减去1来调整下标差异。

 

1、将待排序数组A[1...n]某个任意下标i元素值v作为主元素,简单做法可以直接拿最后元素。

 

2、基于主元素下标i值为v进行左右分区(实际上分区就是分解数组进行排序,类似归并排序的分治法),分区后正序(从小到大)需要满足原则如下:


左边分区值A[1...i - 1] <= 主元素值A[i]右边分区值A[i+1...n] > 主元素值A[i]

倒序(从大到小)则需要满足原则如下:

左边分区值A[1...i - 1] >= 主元素值A[i]右边分区值A[i+1...n] < 主元素值A[i]

下图是正序(从小到大)分区前后抽象图解,如果看完图解还是不理解就直接跳过继续往下看:

 

图解快速排序算法

 

3、将分区后的左右分区(不包含主元素)分别作为子数组递归第12步骤,直到每个子数组小于或等于一个元素为止,递归则开始层层回升,排序完成

 

 

图解快速排序算法

 

图解快速排序算法

定义主元素

根据定义我们知道第一步需要根据给定数组定义一个主元素,我们暂时用最简单方式,拿数组最后一个元素作为主元素(下标=6,=3),图解如下:

图解快速排序算法

分区

根据定义我们得到主元素后接下来进行分区,按分区原则实现图解如下:

 

1、获取主元素

图解快速排序算法


2、定义和维护一个左分区下标计数器变量(leftPartitionIndex),遍历数组下标1-5,一旦发现其中某个元素小于等于主元素,则进行交换。图解如下:

图解快速排序算法


完整排序和分区代码如下:

 

 /** * 快速排序 正序 * * @param param 待排序数组或左右分区子数组参数 * @param beginIndex 数组开始下标 * @param endIndex 数组结束下标,一般是主元素(如果是直接拿最后元素作为主元素时) */ private void quickSortAsc(Integer[] param, int beginIndex, int endIndex) { //当某个分区排序完成后,不再有右分区时,递归开始回升 if (beginIndex < endIndex) { //分区并返回分区界限下标值,实际就是分区后主元素值交换后所在的下标 int partitionIndex = this.partition(param, beginIndex, endIndex); //将左分区作为子数组进行递归快排 this.quickSortAsc(param, beginIndex, partitionIndex - 1); //将右分区作为子数组进行递归快排 this.quickSortAsc(param, partitionIndex + 1, endIndex); }
}

/** * 分区,注意,分区后数组元素下标对应的值会发生移动 * * @param param 待排序数组参数 * @param beginIndex 数组需要排序开始下标 * @param endIndex 数组需要排序结束下标 * @return 分区下标 */ private int partition(Integer[] param, int beginIndex, int endIndex) {
//定义左分区下标变量,默认是开始下标-1,每次交换值时自增加1 int leftPartitionIndex = beginIndex - 1; //主元素值,拿最后元素 int mainEleVal = param[endIndex]; //遍历(数组开始元素-最后元素-1)范围 for (int currentIndex = beginIndex; currentIndex <= endIndex - 1; currentIndex++) { //如果其中某个元素小于等于主元素值 if (param[currentIndex] <= mainEleVal) { //每次交换值时左分区下标变量自增加1 leftPartitionIndex++; //将当前下标对应的值与自增后左分区下标变量对应值进行交换 this.swapElement(param, currentIndex, leftPartitionIndex); } } //每次交换值时左分区下标变量自增加1 leftPartitionIndex++; //将主元素下标对应的值与自增后左分区下标变量对应值进行交换 this.swapElement(param, endIndex, leftPartitionIndex); //当前最后的左分区下标(主元素值所在的下标)作为分区下标 int partitionIndex = leftPartitionIndex; return partitionIndex; }
/** * 交换元素 * * @param param 数组参数 * @param leftIndex 左边索引 * @param rightIndex 右边索引 */ private void swapElement(Integer[] param, int leftIndex, int rightIndex) {
Integer oldMaxIndexVal = param[leftIndex]; param[leftIndex] = param[rightIndex]; param[rightIndex] = oldMaxIndexVal; }
}


3、第一轮执行完上面的分区代码后,就得到了我们上面算法定义时给出的图解如下:

图解快速排序算法

4、继续将左分区进行分区图解如下。注意左分区子数组递归调用的代码在上面已经给出:

图解快速排序算法


分区后,此时,在当前子数组(上一次分区后的原左分区)基础上又产生了左分区子数组,但没有右分区子数组,我们发现此时的左右子数组元素小于等于一个元素,满足上面算法定义第3条,此时(上一次分区后的原左分区)递归开始层层回升,左子数组正序排序完毕。

 

5、继续将右分区进行分区图解如下:

图解快速排序算法


分区后,此时,在当前子数组(上一次分区后的原右分区)基础上又产生了左右分区子数组,我们发现此时的左右子数组元素小于等于一个元素,满足上面算法定义第3条,此时(上一次分区后的原右分区)递归开始层层回升,右子数组正序排序完毕,原左右子数组正序排序完毕后,那么整个数组正序排序完毕。结果如下:

图解快速排序算法

验证算法

最后我们来验证算法,代码如下:

/** * <p> * 快速排序算法 * </p> * * @author laizhiyuan * @since 2019/9/21. */public class QuickSortAlgorithm {

public static void main(String[] args) { Integer[] param = new Integer[]{2, 6, 7, 1, 10, 3};
QuickSortAlgorithm algorithm = new QuickSortAlgorithm();
long t = System.currentTimeMillis(); System.out.println("排序前:" + JSON.toJSONString(param));
//实际代码数组下标从0开始,所以对应length(10)需要减去1 algorithm.quickSortAsc(param, 0, param.length - 1);
System.out.println("排序后:" + JSON.toJSONString(param)); long t2 = System.currentTimeMillis();
System.out.println("算法耗时:" + (t2 - t) + "ms"); }
/** * 快速排序 正序 * * @param param 待排序数组或左右分区子数组参数 * @param beginIndex 数组开始下标 * @param endIndex 数组结束下标,一般是主元素(如果是直接拿最后元素作为主元素时) */ private void quickSortAsc(Integer[] param, int beginIndex, int endIndex) { //当某个分区排序完成后,不再有右分区时,递归开始回升 if (beginIndex < endIndex) { //分区并返回分区界限下标值,实际就是分区后主元素值交换后所在的下标 int partitionIndex = this.partition(param, beginIndex, endIndex); //将左分区作为子数组进行递归快排 this.quickSortAsc(param, beginIndex, partitionIndex - 1); //将右分区作为子数组进行递归快排 this.quickSortAsc(param, partitionIndex + 1, endIndex); }
}

/** * 分区,注意,分区后数组元素下标对应的值会发生移动 * * @param param 待排序数组参数 * @param beginIndex 数组需要排序开始下标 * @param endIndex 数组需要排序结束下标 * @return 分区下标 */ private int partition(Integer[] param, int beginIndex, int endIndex) {
//定义左分区下标变量,默认是开始下标-1,每次交换值时自增加1 int leftPartitionIndex = beginIndex - 1; //主元素值,拿最后元素 int mainEleVal = param[endIndex]; //遍历(数组开始元素-最后元素-1)范围 for (int currentIndex = beginIndex; currentIndex <= endIndex - 1; currentIndex++) { //如果其中某个元素小于等于主元素值 if (param[currentIndex] <= mainEleVal) { //每次交换值时左分区下标变量自增加1 leftPartitionIndex++; //将当前下标对应的值与自增后左分区下标变量对应值进行交换 this.swapElement(param, currentIndex, leftPartitionIndex); } } //每次交换值时左分区下标变量自增加1 leftPartitionIndex++; //将主元素下标对应的值与自增后左分区下标变量对应值进行交换 this.swapElement(param, endIndex, leftPartitionIndex); //当前最后的左分区下标(主元素值所在的下标)作为分区下标 int partitionIndex = leftPartitionIndex; return partitionIndex; }
/** * 交换元素 * * @param param 数组参数 * @param leftIndex 左边索引 * @param rightIndex 右边索引 */ private void swapElement(Integer[] param, int leftIndex, int rightIndex) {
Integer oldMaxIndexVal = param[leftIndex]; param[leftIndex] = param[rightIndex]; param[rightIndex] = oldMaxIndexVal; }
}

执行结果如下:

排序前:[2,6,7,1,10,3]排序后:[1,2,3,6,7,10]算法耗时:117ms


算法时间复杂度

快速排序最坏情况时间复杂度是O(n²)

平均情况时间复杂度是O(nlgn)


算法适用场景

快速排序算法适合小规模(排序元素不多,一般不超过10000)排序需求,大规模排序应该考虑使用其它算法,例如归并排序、堆排序。

 

图解快速排序算法

-END-



图解快速排序算法
     长按扫码关注【Java软件编程之家】公众号,一起学习,一起成长!


 

以上是关于图解快速排序算法的主要内容,如果未能解决你的问题,请参考以下文章

图解算法基础--快速排序,附 Go 代码实现

图解排序算法:快速排序

《图解算法》--快速排序哈希表图广度优先搜索算法

《算法图解》之快速排序

基础排序算法总结(代码+图片分析)

《算法图解》2