JAVA:实现QuickSort快速排序算法(附带源码)
一、项目背景详细介绍
在计算机科学中,排序算法是最基础也是最重要的算法之一,广泛应用于数据库检索、信息统计、数据分析、图形处理等各类场景中。快速排序(QuickSort)作为经常被引用的“分治法之王”,其平均时间复杂度为 $O(n\log n)$,在大多数实际应用中表现优异。
相比于归并排序需要额外的 $O(n)$ 辅助空间,快速排序仅需 $O(\log n)$ 的栈空间进行递归,因此在内存受限环境下更具优势。另外,快速排序的原地排序特性(in-place)也使其在对大规模数组进行排序时,占用的额外内存极少。
二、项目需求详细介绍
-
功能需求
-
在 Java 环境中实现通用的快速排序算法,对整数数组、浮点数数组以及可比较对象数组均能正确排序。
-
支持正序和逆序两种排序方式的切换。
-
-
性能需求
-
平均时间复杂度达到 $O(n\log n)$,并在近乎有序和随机分布两种典型场景下均有良好表现。
-
在小规模分组(如元素个数小于阈值时)切换到插入排序,以进一步提升常数项性能。
-
-
代码质量需求
-
代码结构清晰、模块职责单一;
-
方法注释完备,主流程和关键步骤有详尽解释;
-
具备单元测试用例,覆盖典型场景与边界场景。
-
三、相关技术详细介绍
-
Java 数组与泛型
-
Java 原生数组用于存储基本数据类型和对象引用;
-
泛型(
<T extends Comparable<T>>)用于实现对任意可比较类型的快速排序。
-
-
递归与分治策略
-
快速排序核心在于“分而治之”:先“划分”(partition)出基准点,再对左右子区递归排序;
-
递归调用需控制深度,避免因“最坏划分”产生 $O(n^2)$ 的栈深度。
-
-
插入排序优化
-
当子数组规模较小时,插入排序常数项低,能带来性能提升;
-
常见阈值范围 $5 \sim 15$。
-
-
单元测试(JUnit)
-
使用 JUnit 5 编写测试用例,验证多种数据规模与顺序下算法正确性。
-
四、实现思路详细介绍
-
主流程
-
调用带参
quickSort(array, 0, array.length - 1, ascending); -
当子区间长度小于优化阈值时,调用插入排序;
-
否则进行一次划分(partition),再递归左右子区间。
-
-
划分(Partition)策略
-
常见选取第一个元素、最后一个元素、随机元素或“三数取中”作为枢轴(pivot);
-
本例采用“三数取中”以降低最坏情况概率。
-
双指针方案:
i从左向右找大于枢轴的元素,j从右向左找小于枢轴的元素,交换后继续;最终枢轴放到i与j相遇位置。
-
-
递归结束条件
-
当
low >= high时,无需排序; -
若长度小于阈值,改用插入排序。
-
-
可选逆序
-
通过传入布尔参数
ascending决定比较方向; -
比较时根据
ascending切换>与<。
-
五、完整实现代码
// 文件:QuickSort.java
// 描述:基于三数取中枢轴和插入排序优化的快速排序实现,支持泛型和正/逆序排序
import java.util.Arrays;
public class QuickSort {
/** 插入排序阈值,当子数组长度 <= INSERTION_SORT_THRESHOLD 时使用插入排序 */
private static final int INSERTION_SORT_THRESHOLD = 10;
/**
* 对数组进行快速排序(正序或逆序)
* @param arr 待排序数组
* @param ascending true: 正序,false: 逆序
* @param <T> 数组元素类型,需实现 Comparable 接口
*/
public static <T extends Comparable<T>> void quickSort(T[] arr, boolean ascending) {
if (arr == null || arr.length <= 1) return;
quickSortRecursive(arr, 0, arr.length - 1, ascending);
}
/**
* 递归执行快速排序
* @param arr 待排序数组
* @param low 左边界索引
* @param high 右边界索引
* @param ascending 排序方向
* @param <T> 元素类型
*/
private static <T extends Comparable<T>> void quickSortRecursive(T[] arr, int low, int high, boolean ascending) {
// 若区间足够小,使用插入排序优化
if (high - low + 1 <= INSERTION_SORT_THRESHOLD) {
insertionSort(arr, low, high, ascending);
return;
}
// 三数取中选枢轴,并将枢轴放到 high-1 位置
int pivotIndex = medianOfThree(arr, low, high, ascending);
T pivot = arr[pivotIndex];
// 划分过程
int i = low, j = high - 1;
while (true) {
while (compare(arr[++i], pivot, ascending)) {}
while (compare(pivot, arr[--j], ascending)) {}
if (i < j) {
swap(arr, i, j);
} else {
break;
}
}
// 将枢轴放回正确位置
swap(arr, i, high - 1);
// 递归左右子区间
quickSortRecursive(arr, low, i - 1, ascending);
quickSortRecursive(arr, i + 1, high, ascending);
}
/**
* 三数取中选枢轴,并将枢轴交换到 high-1 位置
* @return 枢轴最终索引
*/
private static <T extends Comparable<T>> int medianOfThree(T[] arr, int low, int high, boolean asc) {
int mid = low + (high - low) / 2;
// 保证 arr[low] <= arr[mid] <= arr[high](或逆序)
if (!compare(arr[low], arr[mid], asc)) swap(arr, low, mid);
if (!compare(arr[low], arr[high], asc)) swap(arr, low, high);
if (!compare(arr[mid], arr[high], asc)) swap(arr, mid, high);
// 将枢轴(中值)移到 high-1
swap(arr, mid, high - 1);
return high - 1;
}
/** 插入排序实现 */
private static <T extends Comparable<T>> void insertionSort(T[] arr, int low, int high, boolean asc) {
for (int i = low + 1; i <= high; i++) {
T key = arr[i];
int j = i - 1;
while (j >= low && !compare(arr[j], key, asc)) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
/** 交换数组中两个元素 */
private static <T> void swap(T[] arr, int i, int j) {
T tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* 比较两个元素
* @return true if a 应排在 b 之前(正序时 a < b;逆序时 a > b)
*/
private static <T extends Comparable<T>> boolean compare(T a, T b, boolean asc) {
return asc ? a.compareTo(b) < 0 : a.compareTo(b) > 0;
}
/** 简单测试入口 */
public static void main(String[] args) {
Integer[] data = { 3, 6, 1, 8, 4, 5, 7, 2, 9, 0 };
System.out.println("原始数据:" + Arrays.toString(data));
quickSort(data, true);
System.out.println("正序结果:" + Arrays.toString(data));
quickSort(data, false);
System.out.println("逆序结果:" + Arrays.toString(data));
}
}
六、代码详细解读
-
quickSort:入口方法,校验空数组后调用递归排序; -
quickSortRecursive:核心递归逻辑,包含插入排序优化、枢轴选取、双指针划分及递归调用; -
medianOfThree:三数取中策略,降低最坏情况概率,并将枢轴临时放置于末尾附近; -
insertionSort:对子区间长度较小时使用插入排序,以减少递归开销; -
swap:通用交换方法; -
compare:根据ascending参数灵活切换正序或逆序的比较逻辑; -
main:演示对整数数组的正序与逆序排序效果。
七、项目详细总结
本项目基于经典快速排序算法,集成了“三数取中”枢轴选取与小规模子区间插入排序优化,兼顾平均性能与常数项开销。通过泛型设计,算法可应用于任意实现了 Comparable 接口的类型,并可灵活切换正序/逆序。该实现具备以下优点:
-
原地排序,空间开销低;
-
平均时间复杂度优异,适用大多数随机分布场景;
-
常数项优化,对小数组使用插入排序进一步提升性能;
-
可定制性强,可轻松替换枢轴策略和优化阈值。
八、项目常见问题及解答
-
问:快速排序稳定吗?
答:本实现属于不稳定排序,若需稳定排序可考虑归并排序或对划分过程做额外处理。 -
问:什么时候会出现最坏情况?
答:当每次枢轴都选到最大或最小元素时,如对已近乎有序数据选用固定枢轴策略,会导致退化为 $O(n^2)$。 -
问:如何避免深度过大导致栈溢出?
答:可在递归时先排序较小子区间,再排序较大子区间,保证最大递归深度为 $O(\log n)$;或改为迭代实现。 -
问:多线程环境能发挥优势吗?
答:可并行地对左右子区间进行排序,但需注意线程开销,一般仅在大规模数据时才推荐。
九、扩展方向与性能优化
-
枢轴选取策略:使用随机枢轴或改进的“三数五点取中”以进一步降低退化概率;
-
尾递归优化:将对较大子区间的递归改为尾递归或迭代,减少栈深度;
-
并行快速排序:引入 Java Fork/Join 框架,对左右子区并行处理,加速大规模排序;
-
混合排序:将插入排序阈值进行动态调优,或与堆排序结合,形成 “Introspective Sort(混合快排)”;
-
外部快速排序:面对海量数据时,采用外部排序策略,将数据分块到磁盘后归并。
更多推荐
所有评论(0)