一、排序算法分类
先明确两个核心概念,帮你理解不同算法的特性:
- 稳定排序:排序后,相等元素的相对位置不变(如归并、冒泡、插入)。
- 不稳定排序:相等元素的相对位置可能改变(如快速、选择)。
- 时间复杂度:算法执行效率的核心指标(O (n²) 为简单算法,O (n log n) 为高效算法)。
一、冒泡排序(Bubble Sort)
1. 核心原理
冒泡排序是最基础的交换类排序,核心思想是:
-
重复遍历待排序数组,两两比较相邻元素,如果顺序错误就交换它们;
-
每一轮遍历都会将当前未排序部分的最大元素 "冒泡" 到末尾;
-
优化点:如果某一轮遍历中没有发生任何交换,说明数组已经有序,可提前终止排序。
public class BubbleSort {
// 整数数组升序排序
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length <= 1) {
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; // 无交换,说明已有序 } } } public static void main(String[] args) { int[] arr = {5, 2, 9, 1, 5, 6}; bubbleSort(arr); System.out.println("冒泡排序结果:" + Arrays.toString(arr)); // [1, 2, 5, 5, 6, 9] }}
核心特点
- 时间复杂度:
- 最坏情况(完全逆序):O(n2)(需要 n-1 轮遍历,每轮 n-i 次比较);
- 最好情况(已排序):O(n)(优化后只需 1 轮遍历,无交换直接终止);
- 平均情况:O(n2)。
- 空间复杂度:O(1)(原地排序,仅使用临时变量交换元素)。
- 稳定性:稳定排序(相邻元素相等时不交换,相对位置不变)。
- 适用场景:仅适合学习排序原理或极小规模 / 近乎有序的数组,实际开发中几乎不使用(效率太低)。
插入排序(简单、适合小规模 / 近乎有序数据)
原理:像整理扑克牌一样,将每个元素插入到前面已排序序列的正确位置。
import java.util.Arrays;
public class InsertionSort {
// 升序排序
public static void insertionSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
// 从第2个元素开始(第1个默认已排序)
for (int i = 1; i < arr.length; i++) {
int current = arr[i]; // 待插入的元素
int j = i - 1; // 已排序序列的末尾指针
// 向前找插入位置:比current大的元素都后移
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j]; // 元素后移
j--;
}
arr[j + 1] = current; // 插入到正确位置
}
}
public static void main(String[] args) {
int[] arr = {5, 2, 9, 1, 5, 6};
insertionSort(arr);
System.out.println("插入排序结果:" + Arrays.toString(arr)); // [1, 2, 5, 5, 6, 9]
}
}
核心特点:
- 时间复杂度:O (n²)(最坏)、O (n)(近乎有序时)。
- 空间复杂度:O (1)(原地排序)。
- 稳定排序,适合小数据量场景。
选择排序(简单、不稳定)
原理:每次从剩余未排序部分找到最小(大)元素,放到已排序部分的末尾。
import java.util.Arrays;
public class SelectionSort {
public static void selectionSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
// 外层循环:确定已排序部分的末尾位置
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i; // 最小元素的索引(初始为当前位置)
// 内层循环:找剩余部分的最小元素
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j; // 更新最小元素索引
}
}
// 交换当前位置和最小元素的位置
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
public static void main(String[] args) {
int[] arr = {5, 2, 9, 1, 5, 6};
selectionSort(arr);
System.out.println("选择排序结果:" + Arrays.toString(arr)); // [1, 2, 5, 5, 6, 9]
}
}
核心特点:
- 时间复杂度:O (n²)(无论数据是否有序,都要遍历找最小值)。
- 空间复杂度:O (1)(原地排序)。
- 不稳定排序(例如 [2, 2, 1] 排序时,第一个 2 会和 1 交换,破坏相对位置)。
归并排序(高效、稳定、分治思想)
原理:采用 "分治" 策略,先将数组拆分成最小单元,再两两合并成有序数组。
import java.util.Arrays;
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
// 临时数组:避免频繁创建数组,优化性能
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
}
// 递归拆分
private static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left >= right) return;
int mid = left + (right - left) / 2; // 避免溢出,等价于 (left+right)/2
// 拆分成左、右两部分
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
// 合并有序的左、右部分
merge(arr, left, mid, right, temp);
}
// 合并逻辑:将 [left,mid] 和 [mid+1,right] 合并为有序数组
private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left; // 左半部分指针
int j = mid + 1; // 右半部分指针
int k = left; // 临时数组指针
// 合并两个有序数组到临时数组
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
// 处理左半部分剩余元素
while (i <= mid) {
temp[k++] = arr[i++];
}
// 处理右半部分剩余元素
while (j <= right) {
temp[k++] = arr[j++];
}
// 将临时数组的有序数据复制回原数组
for (k = left; k <= right; k++) {
arr[k] = temp[k];
}
}
public static void main(String[] args) {
int[] arr = {5, 2, 9, 1, 5, 6};
mergeSort(arr);
System.out.println("归并排序结果:" + Arrays.toString(arr)); // [1, 2, 5, 5, 6, 9]
}
}
核心特点:
- 时间复杂度:O (n log n)(拆分是 log n 层,每层合并是 O (n))。
- 空间复杂度:O (n)(需要临时数组存储合并结果)。
- 稳定排序,适合大数据量、要求稳定排序的场景(如对象排序)。
快速排序(高效、原地、分治思想)
import java.util.Arrays;
public class QuickSortOptimized {
public static void quickSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int left, int right) {
if (left >= right) return;
// 优化1:小规模数据改用插入排序(减少递归开销)
if (right - left + 1 <= 10) {
insertionSort(arr, left, right);
return;
}
// 优化2:三数取中法选基准(避免最坏情况)
int pivotIndex = medianOfThree(arr, left, right);
swap(arr, left, pivotIndex); // 基准放到左边界
// 分区
int pivot = arr[left];
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) j--;
while (i < j && arr[i] <= pivot) i++;
if (i < j) swap(arr, i, j);
}
swap(arr, left, i); // 基准归位
// 递归处理左右
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
// 三数取中法:选左、中、右三个数的中位数作为基准
private static int medianOfThree(int[] arr, int left, int right) {
int mid = left + (right - left) / 2;
if (arr[left] > arr[mid]) swap(arr, left, mid);
if (arr[left] > arr[right]) swap(arr, left, right);
if (arr[mid] > arr[right]) swap(arr, mid, right);
return mid; // 返回中位数索引
}
// 局部插入排序(仅排序 [left, right] 范围)
private static void insertionSort(int[] arr, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int current = arr[i];
int j = i - 1;
while (j >= left && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = current;
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {5, 2, 9, 1, 5, 6, 3, 7};
quickSort(arr);
System.out.println("优化版快速排序结果:" + Arrays.toString(arr)); // [1, 2, 3, 5, 5, 6, 7, 9]
}
}
核心特点:
- 时间复杂度:O (n log n)(平均)、O (n²)(最坏,优化后可避免)。
- 空间复杂度:O (log n)(递归栈空间,原地排序)。
- 不稳定排序,是实际开发中最常用的高效排序算法(JDK 的 Arrays.sort() 底层对基本类型用优化后的快速排序)。
各排序算法对比表
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定 | 适用场景 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 入门学习、小规模数据 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | 简单场景、不要求稳定排序 |
| 快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 | 大数据量、追求高性能 |
| 归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 | 大数据量、要求稳定排序 |
总结
- 简单算法(冒泡 / 插入 / 选择):适合学习排序原理,仅用于小规模数据,实际开发中极少直接使用。
- 高效算法(快速 / 归并):是核心重点,快速排序性能最优(原地 + 低常数),归并排序胜在稳定。
- JDK 内置排序:底层结合了快速排序、归并排序、插入排序的优点(基本类型用双轴快排,对象用归并排序),日常开发优先使用 Arrays.sort()/Collections.sort(),无需手动实现。