📌 引言
交换排序,顾名思义,核心操作就是通过"两两比较并交换"来达到有序的目的 。这一族群里有两个极具代表性的选手:一个是无数人的启蒙算法------冒泡排序 ;另一个则是工业界、面试官长盛不衰的宠儿------快速排序。它们之间,是一场关于执行效率的惊艳飞跃。
1. 冒泡排序(Bubble Sort)
💡 核心思想
从左到开依次比较相邻的两个数,如果前面的数比后面的大,就交换它们。这样一轮下来,当前区间的最大值就像"气泡"一样浮到了数组的最右端。
💻 Java 代码实现(带提前跳出优化)
public class BubbleSort {
public static void sort(int[] arr) {
if (arr == null || arr.length < 2) return;
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
boolean swapped = false; // 优化:若某一趟没有发生任何交换,说明已经有序了
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break;
}
}
}
2. 快速排序(Quick Sort)
🚀 为什么叫快速排序?
冒泡排序太慢了,因为每次交换只能解决相邻 两个元素的无序问题。而快速排序采用分治思想(Divide and Conquer),通过选择一个"基准值",让大小元素横跨大半个数组进行对调,从而实现大步流星的排序。
💡 核心思想
-
找基准(Pivot): 通常选区间第一个数作为基准。
-
分区(Partition): 通过双指针左右开弓,把比 Pivot 小的扔到左边,比 Pivot 大的扔到右边。
-
递归(Solve): 此时 Pivot 已经锁定了它的最终位置。接下来对左、右两部分分别重复上述过程。
💻 Java 代码实现(标准双指针法)
public class QuickSort {
public static void sort(int[] arr) {
if (arr == null || arr.length < 2) return;
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int low, int high) {
if (low >= high) return;
// 核心切分操作,返回基准值的最终正确索引
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1); // 递归搞定左半部分
quickSort(arr, pivotIndex + 1, high); // 递归搞定右半部分
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[low];
int i = low, j = high + 1;
while (true) {
// 左指针向右找第一个比 pivot 大的数
while (arr[++i] < pivot) if (i == high) break;
// 右指针向左找第一个比 pivot 小的数
while (pivot < arr[--j]) if (j == low) break;
if (i >= j) break; // 指针相遇,退出循环
swap(arr, i, j); // 交换这两个错位的数
}
swap(arr, low, j); // 将基准值交换到它的最终正确位置 `j`
return j;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
📊 交换排序大比拼
| 算法 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
| 快速排序 | O(n \log n) | O(n^2) | O(n \log n) | O(\log n) (递归栈) | 不稳定 |