快速排序是一种典型的分治算法。它选择一个基准值,把数组拆分成“小于基准”和“大于基准”的两部分,再递归处理左右区间。平均时间复杂度是 O(n log n),在理解排序算法、递归和分治思想时非常重要。
整体流程
基础思想
快速排序包含三步:
- 选择基准值 pivot。
- 分区:把小于 pivot 的元素放左边,大于 pivot 的元素放右边。
- 递归排序左右区间。
示例代码:
1 | public void quickSort(int[] arr, int left, int right) { |
这段代码使用最后一个元素作为 pivot,便于理解,但生产实现通常会使用更稳健的 pivot 选择策略。
Pivot 选择和递归边界
快速排序的性能很依赖 pivot。如果每次 pivot 都把数组切得很不均匀,递归深度会接近 n,最坏时间复杂度会退化到 O(n^2)。例如数据已经按订单创建时间升序排列,而实现又总是选择最后一个元素作为 pivot,就容易出现这种问题。
一个常见优化是随机选择 pivot:
1 | private int randomizedPartition(int[] arr, int left, int right) { |
还有一种做法是“三数取中”,从左端、中间、右端选一个更接近中位数的值作为 pivot。它不能完全避免最坏情况,但能降低有序数据导致退化的概率。
递归边界也要写清楚:当 left >= right 时直接返回。否则空区间、单元素区间会继续递归,轻则浪费调用栈,重则造成栈溢出。
供应链业务例子
假设订单履约系统需要把待处理订单按综合优先级排序:
1 | 客户等级 |
如果订单量很大,排序应该优先下推到数据库或搜索引擎,让索引和分页机制发挥作用:
1 | SELECT * |
如果是在内存中对少量候选订单做二次排序,可以使用 Java 内置排序:
1 | orders.sort(Comparator |
学习快速排序的意义,不是让业务代码手写排序,而是理解“分而治之”的思路。比如订单分仓、波次拆分、库存重算,都可以把大任务拆成多个小区间并行处理。
分治思想在履约任务里的应用
分治思想在供应链系统中很常见。假设一天有几十万张待履约订单,如果直接由一个任务串行处理,会遇到处理时间长、失败重试成本高、单点压力大的问题。更合理的做法是先拆分:
1 | 按仓库拆分 -> 按承诺发货日期拆分 -> 按波次拆分 -> 每个分片独立处理 |
拆分以后,每个任务只处理一个相对小的订单集合。失败时可以只重试某个仓库、某个波次,而不是重跑整批数据。
在 Java 里可以用线程池并行处理这些分片:
1 | for (OrderShard shard : shards) { |
这里和快速排序类似:先把大问题切小,再分别处理,最后合并结果。区别是排序算法合并的是有序区间,业务系统合并的是处理状态、异常结果和监控指标。
复杂度和风险
快速排序特点:
- 平均时间复杂度:
O(n log n)。 - 最坏时间复杂度:
O(n^2),通常发生在 pivot 选择很差且数据分区极不均衡时。 - 空间复杂度:平均
O(log n),来自递归调用栈。 - 稳定性:通常不稳定。
为了降低最坏情况风险,可以使用随机 pivot、三数取中,或者在小区间切换为插入排序。JDK 内部排序实现已经处理了很多细节,业务代码一般不要重复造轮子。
小结
快速排序的核心价值是分治。供应链系统中,排序只是表象,更重要的是理解如何拆分大任务、减少无效扫描、控制单次处理的数据量。真正落地时,要优先使用数据库排序、索引、分页和 JDK 排序工具。