图解快速排序算法
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、将分区后的左右分区(不包含主元素)分别作为子数组递归第1、2步骤,直到每个子数组小于或等于一个元素为止,递归则开始层层回升,排序完成
图解快速排序算法
定义主元素
根据定义我们知道第一步需要根据给定数组定义一个主元素,我们暂时用最简单方式,拿数组最后一个元素作为主元素(下标=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-
以上是关于图解快速排序算法的主要内容,如果未能解决你的问题,请参考以下文章