八大经典排序算法的 Java 实现&对比(冒泡、选择、插入、希尔、归并、快排、计数、堆排序)【面试必考】
以下是八大经典排序算法的Java实现,包含时间复杂度、空间复杂度分析、特点说明及优化策略:
1. 冒泡排序 (Bubble Sort)
public void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
boolean swapped = false; // 优化:标记是否发生交换
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
swapped = true;
}
}
if (!swapped) break; // 无交换说明已有序
}
}
- 时间复杂度:
- 最好:O(n)(已有序)
- 最坏:O(n²)
- 平均:O(n²)
- 空间复杂度:O(1)(原地排序)
- 特点:
- 稳定排序
- 简单但效率低
- 优化:
- 添加
swapped标志提前终止
- 添加
2. 选择排序 (Selection Sort)
public void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) minIdx = j;
}
swap(arr, i, minIdx);
}
}
- 时间复杂度:O(n²)(始终需遍历剩余元素)
- 空间复杂度:O(1)
- 特点:
- 不稳定(如
[5, 5, 2]) - 交换次数少(最多 n-1 次)
- 不稳定(如
3. 插入排序 (Insertion Sort)
public void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
- 时间复杂度:
- 最好:O(n)(已有序)
- 最坏:O(n²)
- 空间复杂度:O(1)
- 特点:
- 稳定排序
- 对小规模/基本有序数据高效
- 优化:
- 二分插入排序(减少比较次数)
4. 希尔排序 (Shell Sort)
public void shellSort(int[] arr) {
int n = arr.length;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
- 时间复杂度:
- 平均:O(n log n) ~ O(n²)(取决于步长序列)
- 最优:O(n log² n)(Knuth序列)
- 空间复杂度:O(1)
- 特点:
- 不稳定(跨步长交换)
- 插入排序的改进版
5. 归并排序 (Merge Sort)
public void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
private void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
System.arraycopy(temp, 0, arr, left, temp.length);
}
- 时间复杂度:O(n log n)(始终)
- 空间复杂度:O(n)(需额外数组)
- 特点:
- 稳定排序
- 适合链表/外部排序
- 优化:
- 小规模子数组用插入排序
6. 快速排序 (Quick Sort)
public void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivot = partition(arr, low, high);
quickSort(arr, low, pivot - 1);
quickSort(arr, pivot + 1, high);
}
}
private int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 基准选最右元素
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, high);
return i + 1;
}
- 时间复杂度:
- 最好:O(n log n)
- 最坏:O(n²)(数组已有序)
- 空间复杂度:O(log n)(递归栈)
- 特点:
- 不稳定(如
[3, 2, 2]) - 平均性能最快
- 不稳定(如
- 优化:
- 三数取中法选基准
- 小数组转插入排序
- 尾递归优化
7. 堆排序 (Heap Sort)
public void heapSort(int[] arr) {
int n = arr.length;
// 建堆(从最后一个非叶子节点开始)
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 逐个提取堆顶元素
for (int i = n - 1; i > 0; i--) {
swap(arr, 0, i);
heapify(arr, i, 0);
}
}
private void heapify(int[] arr, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) largest = left;
if (right < n && arr[right] > arr[largest]) largest = right;
if (largest != i) {
swap(arr, i, largest);
heapify(arr, n, largest);
}
}
- 时间复杂度:O(n log n)(建堆 O(n),调整 O(log n))
- 空间复杂度:O(1)
- 特点:
- 不稳定(堆调整可能破坏顺序)
- 适合取 Top K 问题
8. 计数排序 (Counting Sort)
public void countingSort(int[] arr) {
int max = Arrays.stream(arr).max().getAsInt();
int min = Arrays.stream(arr).min().getAsInt();
int range = max - min + 1;
int[] count = new int[range];
int[] output = new int[arr.length];
for (int num : arr) count[num - min]++;
for (int i = 1; i < range; i++) count[i] += count[i - 1];
for (int i = arr.length - 1; i >= 0; i--) {
output[count[arr[i] - min] - 1] = arr[i];
count[arr[i] - min]--;
}
System.arraycopy(output, 0, arr, 0, arr.length);
}
- 时间复杂度:O(n + k)(k 为数据范围)
- 空间复杂度:O(n + k)
- 特点:
- 稳定排序(反向填充)
- 仅适用于整数且范围小的情况
总结对比表
| 排序算法 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 希尔排序 | O(n log n) | O(n²) | O(n^{1.3}) | O(1) | 不稳定 |
| 归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
| 快速排序 | O(n log n) | O(n²) | O(n log n) | O(log n) | 不稳定 |
| 堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
| 计数排序 | O(n + k) | O(n + k) | O(n + k) | O(n + k) | 稳定 |
使用场景建议
- 小规模数据:插入排序(简单且稳定)
- 通用高效排序:快速排序(平均性能最优)
- 需要稳定性:归并排序(稳定且 O(n log n))
- 数据范围有限:计数排序(如年龄排序)
- 外部排序:归并排序(处理大文件)
- Top K 问题:堆排序(无需全排序)
实际开发中优先使用标准库(如 Arrays.sort()),其内部根据数据特征自动选择最优算法(如插入+归并+快排组合)。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120343

浙公网安备 33010602011771号