本文完整梳理冒泡、插入、选择、快速、归并、计数、桶、基数8 种排序算法的核心逻辑,并提供可直接运行的 Java 代码示例,同时标注每种算法的关键特性(时间复杂度、稳定性、适用场景),帮你一站式掌握排序算法的落地实现。
先明确核心评价维度
表格
| 指标 | 说明 |
|---|---|
| 时间复杂度 | 算法执行时间与数据量 n 的关系(最好 / 最坏 / 平均) |
| 空间复杂度 | 额外占用的内存空间(O (1) 为「原地排序」,最优) |
| 稳定性 | 排序后,值相等的元素相对位置不变(如 [2,2,1] 排序后,两个 2 的位置不颠倒) |
1. 冒泡排序(Bubble Sort)
核心原理
相邻元素两两比较,逆序则交换,每轮将最大元素 "冒泡" 到末尾;可优化:若某轮无交换,说明已排序,直接终止。
关键特性
- 时间复杂度:最好 O (n)、最坏 O (n²)、平均 O (n²)
- 空间复杂度:O (1)(原地排序)
- 稳定性:稳定
Java Demo
java
运行
public class BubbleSort {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
int n = arr.length;
// 标记是否发生交换,优化版本
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
// 每轮结束后,最后i个元素已排序,无需再比较
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 = {3, 1, 4, 1, 5, 9, 2, 6};
bubbleSort(arr);
System.out.println("冒泡排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
2. 选择排序(Selection Sort)
核心原理
将数组分为 "已排序区" 和 "未排序区",每轮找到未排序区的最小值,放到已排序区末尾。
关键特性
- 时间复杂度:最好 / 最坏 / 平均都是 O (n²)
- 空间复杂度:O (1)(原地排序)
- 稳定性:不稳定(交换会破坏相等元素相对位置)
Java Demo
java
运行
public class SelectionSort {
public static void selectionSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
// 记录最小值的索引(关键:先找索引,最后再交换)
int minIndex = i;
for (int j = i + 1; j < n; 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 = {3, 1, 4, 1, 5, 9, 2, 6};
selectionSort(arr);
System.out.println("选择排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
3. 插入排序(Insertion Sort)
核心原理
类似整理扑克牌,将数组分为 "已排序区"(初始为第一个元素)和 "未排序区",逐个将未排序元素插入到已排序区的正确位置。
关键特性
- 时间复杂度:最好 O (n)、最坏 O (n²)、平均 O (n²)
- 空间复杂度:O (1)(原地排序)
- 稳定性:稳定
Java Demo
java
运行
public class InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
int n = arr.length;
for (int i = 1; i < n; i++) {
// 待插入的元素
int temp = arr[i];
// 已排序区的末尾指针
int j = i - 1;
// 找到插入位置(已排序区元素大于temp则后移)
while (j >= 0 && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
// 插入元素
arr[j + 1] = temp;
}
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6};
insertionSort(arr);
System.out.println("插入排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
4. 快速排序(Quick Sort)
核心原理
分治思想:选 "基准值"(pivot),将数组分为 "小于基准""等于基准""大于基准" 三部分,递归排序左右两部分;优化:随机选基准避免最坏情况。
关键特性
- 时间复杂度:最好 O (nlogn)、最坏 O (n²)、平均 O (nlogn)
- 空间复杂度:O (logn)(递归栈空间)
- 稳定性:不稳定
Java Demo
java
运行
import java.util.Arrays;
import java.util.Random;
public class QuickSort {
private static final Random random = new Random();
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;
// 随机选基准,避免有序数组导致的最坏情况
int pivotIndex = left + random.nextInt(right - left + 1);
// 将基准交换到左边界
swap(arr, left, pivotIndex);
int pivot = arr[left];
// 分区:[left+1, lt] < pivot,[lt+1, i) = pivot,[gt, right] > pivot
int lt = left; // 小于区的右边界
int gt = right + 1; // 大于区的左边界
int i = left + 1; // 当前遍历指针
while (i < gt) {
if (arr[i] < pivot) {
lt++;
swap(arr, i, lt);
i++;
} else if (arr[i] > pivot) {
gt--;
swap(arr, i, gt);
} else {
i++;
}
}
// 将基准放到小于区和等于区的中间
swap(arr, left, lt);
// 递归排序左右部分
quickSort(arr, left, lt - 1);
quickSort(arr, gt, right);
}
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 = {3, 1, 4, 1, 5, 9, 2, 6};
quickSort(arr);
System.out.println("快速排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
5. 归并排序(Merge Sort)
核心原理
分治思想:将数组递归拆分为两半,分别排序后,再合并两个有序子数组;核心是 "合并" 操作。
关键特性
- 时间复杂度:最好 / 最坏 / 平均都是 O (nlogn)
- 空间复杂度:O (n)(需要临时数组存储合并结果)
- 稳定性:稳定
Java Demo
java
运行
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;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
// 治:合并两个有序子数组
merge(arr, left, mid, right, temp);
}
private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
// 复制数据到临时数组
System.arraycopy(arr, left, temp, left, right - left + 1);
int i = left; // 左子数组指针
int j = mid + 1; // 右子数组指针
int k = left; // 原数组指针
// 合并两个有序子数组
while (i <= mid && j <= right) {
if (temp[i] <= temp[j]) {
arr[k++] = temp[i++];
} else {
arr[k++] = temp[j++];
}
}
// 复制左子数组剩余元素
while (i <= mid) {
arr[k++] = temp[i++];
}
// 右子数组剩余元素无需复制(已在原数组中)
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6};
mergeSort(arr);
System.out.println("归并排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
6. 计数排序(Counting Sort)
核心原理
非比较排序:统计每个数值出现的次数,再根据次数重构有序数组;仅适用于数值范围小且为整数的场景。
关键特性
- 时间复杂度:O (n + k)(k 为数值范围)
- 空间复杂度:O (k)(计数数组空间)
- 稳定性:稳定(可优化实现)
Java Demo
java
运行
import java.util.Arrays;
public class CountingSort {
public static void countingSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
// 步骤1:找到数组的最大值和最小值
int min = arr[0], max = arr[0];
for (int num : arr) {
if (num < min) min = num;
if (num > max) max = num;
}
// 步骤2:创建计数数组,统计每个数值的出现次数
int[] count = new int[max - min + 1];
for (int num : arr) {
count[num - min]++;
}
// 步骤3:重构有序数组(稳定版:累加计数数组)
// 累加计数数组,得到每个数值的最后位置
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 倒序遍历原数组,保证稳定性
int[] temp = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int index = count[arr[i] - min] - 1;
temp[index] = arr[i];
count[arr[i] - min]--;
}
// 复制回原数组
System.arraycopy(temp, 0, arr, 0, arr.length);
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6};
countingSort(arr);
System.out.println("计数排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
7. 桶排序(Bucket Sort)
核心原理
将数据分配到多个 "桶" 中,每个桶内单独排序(可用插入排序 / 快速排序),最后合并所有桶;适用于数据分布均匀的场景。
关键特性
- 时间复杂度:最好 O (n + k)、最坏 O (n²)、平均 O (n + k)(k 为桶数)
- 空间复杂度:O (n + k)
- 稳定性:稳定(取决于桶内排序算法)
Java Demo
java
运行
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class BucketSort {
public static void bucketSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
// 步骤1:确定桶的数量和范围
int min = arr[0], max = arr[0];
for (int num : arr) {
if (num < min) min = num;
if (num > max) max = num;
}
// 桶的数量:根据数据分布调整,这里取10个桶
int bucketNum = 10;
List<List<Integer>> buckets = new ArrayList<>(bucketNum);
// 初始化桶
for (int i = 0; i < bucketNum; i++) {
buckets.add(new ArrayList<>());
}
// 步骤2:将数据分配到对应桶中
for (int num : arr) {
// 计算数据所属桶的索引
int bucketIndex = (num - min) * (bucketNum - 1) / (max - min);
buckets.get(bucketIndex).add(num);
}
// 步骤3:每个桶内排序,再合并
int index = 0;
for (List<Integer> bucket : buckets) {
// 桶内用插入排序/Collections.sort(底层是归并排序)
Collections.sort(bucket);
for (int num : bucket) {
arr[index++] = num;
}
}
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6};
bucketSort(arr);
System.out.println("桶排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
8. 基数排序(Radix Sort)
核心原理
非比较排序:按数字的 "位"(个位、十位、百位...)依次排序,每轮用计数排序 / 桶排序保证稳定性;适用于整数 / 字符串排序。
关键特性
- 时间复杂度:O (d*(n + k))(d 为位数,k 为基数,如十进制 k=10)
- 空间复杂度:O (n + k)
- 稳定性:稳定
Java Demo
java
运行
import java.util.Arrays;
public class RadixSort {
public static void radixSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
// 步骤1:找到最大值,确定最大位数
int max = arr[0];
for (int num : arr) {
if (num > max) max = num;
}
// 步骤2:按位排序(个位、十位、百位...)
for (int exp = 1; max / exp > 0; exp *= 10) {
countingSortByDigit(arr, exp);
}
}
// 按指定位(exp=1:个位,exp=10:十位)进行计数排序
private static void countingSortByDigit(int[] arr, int exp) {
int n = arr.length;
int[] output = new int[n];
int[] count = new int[10]; // 十进制,0-9
// 统计当前位的数字出现次数
for (int num : arr) {
int digit = (num / exp) % 10;
count[digit]++;
}
// 累加计数数组,确定位置
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
// 倒序遍历,保证稳定性
for (int i = n - 1; i >= 0; i--) {
int digit = (arr[i] / exp) % 10;
output[count[digit] - 1] = arr[i];
count[digit]--;
}
// 复制回原数组
System.arraycopy(output, 0, arr, 0, n);
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6};
radixSort(arr);
System.out.println("基数排序结果:" + Arrays.toString(arr));
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
}
}
核心总结(8 种排序对比)
表格
| 排序算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
|---|---|---|---|---|
| 冒泡 | O(n²) | O(1) | 稳定 | 小数据量、几乎有序的数据 |
| 选择 | O(n²) | O(1) | 不稳定 | 小数据量、交换成本高的场景 |
| 插入 | O(n²) | O(1) | 稳定 | 小数据量、几乎有序的数据 |
| 快速 | O(nlogn) | O(logn) | 不稳定 | 通用场景、内存排序首选 |
| 归并 | O(nlogn) | O(n) | 稳定 | 需稳定排序、外排序(磁盘) |
| 计数 | O(n+k) | O(k) | 稳定 | 数值范围小的整数排序 |
| 桶 | O(n+k) | O(n+k) | 稳定 | 数据分布均匀的场景 |
| 基数 | O(d*(n+k)) | O(n+k) | 稳定 | 整数 / 字符串、位数固定的场景 |
关键选型建议
- 通用场景优先选快速排序(性能最优);
- 需稳定排序选归并排序;
- 数值范围小选计数排序 ,数据分布均匀选桶排序 ,整数 / 字符串选基数排序;
- 小数据量(n<1000)选插入排序(实际性能优于快速排序)。