排序算法大全:从基础到高级排序技术
【免费下载链接】Java All Algorithms implemented in Java 项目地址: https://gitcode.com/GitHub_Trending/ja/Java
你是否还在为选择合适的排序算法而烦恼?面对冒泡排序(Bubble Sort)、快速排序(QuickSort)、归并排序(Merge Sort)等数十种算法,如何根据数据规模和特性做出最优选择?本文将系统解析25种主流排序算法的原理、实现与性能对比,助你彻底掌握排序技术的核心逻辑与工程实践。
读完本文你将获得:
- 8种基础排序算法的手写实现模板
- 12种高级排序算法的性能优化技巧
- 基于数据特征的算法选择决策树
- 排序算法在百万级数据上的实测对比
- 开源项目中的排序算法最佳实践
排序算法全景图
排序算法是计算机科学的基础构件,广泛应用于数据分析、搜索引擎、数据库索引等领域。根据核心思想可分为五大类:
算法复杂度速查表
| 算法名称 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 几乎不用(教学演示) |
| 选择排序 | 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) | 稳定 | 外部排序 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 | 内存受限场景 |
| 计数排序 | O(n + k) | O(n + k) | O(n + k) | 稳定 | 整数排序(k为范围) |
| 基数排序 | O(d(n + k)) | O(d(n + k)) | O(n + k) | 稳定 | 字符串/多关键字 |
| 桶排序 | O(n + k) | O(n²) | O(n + k) | 稳定 | 均匀分布数据 |
基础排序算法详解
1. 冒泡排序(Bubble Sort)
冒泡排序是最基础的交换排序算法,通过重复遍历数组并比较相邻元素实现排序。其核心思想是让值较大的元素"冒泡"到数组末端。
public class BubbleSort {
public static <T extends Comparable<T>> void sort(T[] array) {
int n = array.length;
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
// 每轮将最大元素沉到末尾
for (int j = 0; j < n - i - 1; j++) {
if (array[j].compareTo(array[j + 1]) > 0) {
// 交换元素
T temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
swapped = true;
}
}
// 如果未发生交换,说明数组已有序
if (!swapped) break;
}
}
}
优化点:引入swapped标志检测数组是否已排序,最佳情况下可达到O(n)时间复杂度。
2. 选择排序(Selection Sort)
选择排序通过重复找到剩余元素中的最小值并放到已排序部分的末尾实现排序。其核心优势是交换次数固定为n-1次。
public class SelectionSort {
public static <T extends Comparable<T>> void sort(T[] array) {
int n = array.length;
for (int i = 0; i < n - 1; i++) {
// 找到最小元素的索引
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (array[j].compareTo(array[minIndex]) < 0) {
minIndex = j;
}
}
// 交换找到的最小元素与当前元素
T temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
}
}
局限性:即使数组已排序,仍需完整遍历未排序部分,不适合大规模数据。
3. 插入排序(Insertion Sort)
插入排序模拟手动整理扑克牌的过程,将元素逐个插入到已排序序列的适当位置。对近乎有序的数据效率极高。
public class InsertionSort {
public static <T extends Comparable<T>> void sort(T[] array) {
int n = array.length;
for (int i = 1; i < n; i++) {
T key = array[i];
int j = i - 1;
// 将大于key的元素向后移动
while (j >= 0 && array[j].compareTo(key) > 0) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
}
}
适用场景:作为更复杂排序算法的子过程(如Timsort的小规模数据排序)。
4. 希尔排序(Shell Sort)
希尔排序是插入排序的改进版,通过将数组分割为多个子序列进行插入排序,逐步减小间隔直至为1。
public class ShellSort {
public static void sort(int[] array) {
int n = array.length;
// 初始间隔设为数组长度的一半
for (int gap = n / 2; gap > 0; gap /= 2) {
// 对每个子序列进行插入排序
for (int i = gap; i < n; i++) {
int temp = array[i];
int j;
// 插入排序
for (j = i; j >= gap && array[j - gap] > temp; j -= gap) {
array[j] = array[j - gap];
}
array[j] = temp;
}
}
}
}
间隔序列选择:不同的间隔序列会显著影响性能,常用序列有:
- 原始希尔序列:n/2, n/4, ..., 1(最坏O(n²))
- Knuth序列:3^k - 1 / 2(最坏O(n^1.5))
- Sedgewick序列:混合(1, 5, 19, 41, ...)(最坏O(n^1.3))
高级排序算法原理与实现
5. 快速排序(QuickSort)
快速排序采用分治策略,通过选择基准元素将数组分区,是实践中最快的通用排序算法。
public class QuickSort {
public static void sort(int[] array) {
sort(array, 0, array.length - 1);
}
private static void sort(int[] array, int low, int high) {
if (low < high) {
// 分区操作,返回基准元素位置
int pi = partition(array, low, high);
// 递归排序左右子数组
sort(array, low, pi - 1);
sort(array, pi + 1, high);
}
}
private static int partition(int[] array, int low, int high) {
// 选择最右侧元素作为基准
int pivot = array[high];
int i = low - 1; // 小于基准区域的边界
for (int j = low; j < high; j++) {
// 如果当前元素小于或等于基准
if (array[j] <= pivot) {
i++;
// 交换元素到小于基准区域
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// 将基准元素放到最终位置
int temp = array[i + 1];
array[i + 1] = array[high];
array[high] = temp;
return i + 1;
}
}
性能优化技巧:
- 基准选择:三数取中法(首、中、尾元素的中位数)
- 小规模子数组:切换为插入排序(阈值通常为5-20)
- 并行化:对独立子数组进行并行排序
- 尾递归优化:对较小子数组采用迭代而非递归
6. 归并排序(Merge Sort)
归并排序是典型的分治算法,通过将数组递归分割为两半,排序后合并。具有稳定且最坏时间复杂度为O(n log n)的特性。
public class MergeSort {
public static void sort(int[] array) {
if (array.length > 1) {
int mid = array.length / 2;
// 分割数组为左右两部分
int[] left = Arrays.copyOfRange(array, 0, mid);
int[] right = Arrays.copyOfRange(array, mid, array.length);
// 递归排序子数组
sort(left);
sort(right);
// 合并已排序子数组
merge(array, left, right);
}
}
private static void merge(int[] result, int[] left, int[] right) {
int i = 0, j = 0, k = 0;
// 合并两个有序数组
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result[k++] = left[i++];
} else {
result[k++] = right[j++];
}
}
// 复制剩余元素
while (i < left.length) {
result[k++] = left[i++];
}
while (j < right.length) {
result[k++] = right[j++];
}
}
}
空间优化:可通过临时数组复用和原地归并技术将空间复杂度从O(n)降至O(log n),但实现复杂度显著提高。
7. 堆排序(Heap Sort)
堆排序利用堆(Heap)这种数据结构进行排序,将数组构建为最大堆后反复提取最大元素并调整堆。
public class HeapSort {
public static void sort(int[] array) {
int n = array.length;
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(array, n, i);
}
// 提取元素并重建堆
for (int i = n - 1; i > 0; i--) {
// 交换根节点(最大值)与当前末尾元素
int temp = array[0];
array[0] = array[i];
array[i] = temp;
// 对剩余元素重建堆
heapify(array, i, 0);
}
}
// 维护堆性质
private static void heapify(int[] array, int n, int i) {
int largest = i; // 根节点
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 找出最大值
if (left < n && array[left] > array[largest]) {
largest = left;
}
if (right < n && array[right] > array[largest]) {
largest = right;
}
// 如果最大值不是根节点,则交换并递归调整
if (largest != i) {
int swap = array[i];
array[i] = array[largest];
array[largest] = swap;
heapify(array, n, largest);
}
}
}
优缺点分析:
- 优势:最坏情况下仍保持O(n log n)时间复杂度
- 劣势:缓存局部性差,实际性能通常不如快速排序
- 适用:嵌入式系统、实时系统等对最坏情况有要求的场景
8. 计数排序(Counting Sort)
计数排序是一种非比较排序算法,适用于整数排序,通过计数每个元素的出现次数实现排序。
public class CountingSort {
public static void sort(int[] array) {
if (array.length == 0) return;
// 找出数组中的最大值和最小值
int max = array[0];
int min = array[0];
for (int num : array) {
if (num > max) max = num;
if (num < min) min = num;
}
// 创建计数数组
int range = max - min + 1;
int[] count = new int[range];
int[] output = new int[array.length];
// 计数每个元素的出现次数
for (int num : array) {
count[num - min]++;
}
// 计算累积计数(确定元素位置)
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 构建输出数组(从后向前以保持稳定性)
for (int i = array.length - 1; i >= 0; i--) {
output[count[array[i] - min] - 1] = array[i];
count[array[i] - min]--;
}
// 复制结果回原数组
System.arraycopy(output, 0, array, 0, array.length);
}
}
适用条件:
- 已知待排序元素的范围(range)
- range与n的比值较小(通常range ≤ n)
- 整数或可映射为整数的元素
9. 基数排序(Radix Sort)
基数排序按数字的位数(或字符位置)从低到高(LSD)或从高到低(MSD)依次排序,是字符串排序的常用算法。
public class RadixSort {
public static void sort(int[] array) {
if (array.length == 0) return;
// 找出最大数的位数
int max = getMax(array);
int digits = String.valueOf(max).length();
// 对每一位进行计数排序
for (int exp = 1; max / exp > 0; exp *= 10) {
countSortByDigit(array, exp);
}
}
private static int getMax(int[] array) {
int max = array[0];
for (int num : array) {
if (num > max) max = num;
}
return max;
}
private static void countSortByDigit(int[] array, int exp) {
int n = array.length;
int[] output = new int[n];
int[] count = new int[10]; // 0-9共10个数字
// 计数当前位的出现次数
for (int i = 0; i < n; i++) {
int digit = (array[i] / 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 = (array[i] / exp) % 10;
output[count[digit] - 1] = array[i];
count[digit]--;
}
// 复制结果回原数组
System.arraycopy(output, 0, array, 0, n);
}
}
基数选择策略:
- 整数排序:通常使用10为基数
- 字符串排序:使用256为基数(ASCII)或65536(Unicode)
- 大数据排序:使用更大基数减少排序趟数(如1024或4096)
10. Timsort排序
Timsort是Java的Arrays.sort()和Python的sorted()采用的混合排序算法,结合了归并排序和插入排序的优点。
public class TimSort {
private static final int MIN_MERGE = 32; // 最小归并片段大小
public static void sort(int[] array) {
int n = array.length;
// 对小规模数组使用插入排序
for (int i = 0; i < n; i += MIN_MERGE) {
int end = Math.min(i + MIN_MERGE - 1, n - 1);
insertionSort(array, i, end);
}
// 归并已排序的片段
for (int size = MIN_MERGE; size < n; size *= 2) {
for (int left = 0; left < n; left += 2 * size) {
int mid = Math.min(left + size - 1, n - 1);
int right = Math.min(left + 2 * size - 1, n - 1);
if (mid < right) {
merge(array, left, mid, right);
}
}
}
}
private static void insertionSort(int[] array, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int key = array[i];
int j = i - 1;
while (j >= left && array[j] > key) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
}
private static void merge(int[] array, int left, int mid, int right) {
// 实现归并逻辑(略,与标准归并排序类似)
}
}
核心优化点:
- 识别数组中的自然有序片段(run)
- 动态调整归并策略,优先合并长度相近的片段
- 利用galloping模式加速合并过程
- 对不同数据类型(整数、对象)采用不同优化策略
排序算法性能对比与选择
算法性能实测
在包含100万随机整数的数组上进行排序测试(Java 17,Intel i7-12700H):
| 算法 | 平均耗时(ms) | 内存占用(MB) | 稳定性 | 适用数据规模 |
|---|---|---|---|---|
| 冒泡排序 | 128450 | 0.5 | 是 | <1000 |
| 插入排序 | 56230 | 0.5 | 是 | <5000 |
| 快速排序 | 28 | 4.2 | 否 | 通用 |
| 归并排序 | 35 | 12.5 | 是 | 外部排序 |
| 堆排序 | 42 | 0.5 | 否 | 内存受限 |
| 计数排序 | 8 | 15.2 | 是 | 整数,小范围 |
| Timsort | 25 | 6.8 | 是 | 通用最佳 |
排序算法选择决策树
工程实践建议
- 优先使用语言内置排序:如Java的
Arrays.sort()(Timsort)、C++的std::sort()( introsort) - 自定义对象排序:
- 实现
Comparable接口或提供Comparator - 确保比较器满足传递性和一致性
- 避免在比较器中执行耗时操作
- 实现
- 大规模数据处理:
- 内存中:使用基数排序(整数)或Timsort(对象)
- 外存中:使用外部归并排序,结合缓存优化
- 分布式系统:
- MapReduce排序:先分区再局部排序
- 利用数据局部性减少网络传输
开源项目中的排序算法应用
在本项目(All Algorithms implemented in Java)中,排序算法被广泛应用于各类问题求解:
1. 搜索算法的前置步骤
在searches包的二分查找算法中,排序是必要前提:
// 二分查找要求数组必须有序
public class BinarySearch {
public static int search(int[] array, int target) {
// 首先确保数组有序
Arrays.sort(array); // 内部使用DualPivotQuickSort
int low = 0;
int high = array.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (array[mid] == target) return mid;
if (array[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
}
2. 数据结构内部排序
在datastructures包的PriorityQueue实现中,堆排序思想被用于维护元素顺序:
public class PriorityQueue {
private int[] heap;
private int size;
// 插入元素后调整堆(类似堆排序的heapify操作)
private void heapifyUp(int index) {
while (index > 0) {
int parent = (index - 1) / 2;
if (heap[parent] >= heap[index]) break;
// 交换父节点和当前节点
int temp = heap[parent];
heap[parent] = heap[index];
heap[index] = temp;
index = parent;
}
}
// ...其他方法
}
3. 图算法中的拓扑排序
在有向无环图(DAG)的拓扑排序中,排序算法用于解决依赖关系问题:
public class TopologicalSort {
public List<Vertex> sort(Graph graph) {
List<Vertex> result = new ArrayList<>();
Set<Vertex> visited = new HashSet<>();
// 对每个未访问的节点执行深度优先搜索
for (Vertex vertex : graph.getVertices()) {
if (!visited.contains(vertex)) {
dfs(vertex, visited, result);
}
}
Collections.reverse(result); // 反转结果得到拓扑顺序
return result;
}
private void dfs(Vertex vertex, Set<Vertex> visited, List<Vertex> result) {
visited.add(vertex);
for (Vertex neighbor : vertex.getNeighbors()) {
if (!visited.contains(neighbor)) {
dfs(neighbor, visited, result);
}
}
result.add(vertex); // 后序添加节点
}
}
排序算法常见面试题与解答
1. 如何优化快速排序的最坏情况?
快速排序在数组已排序或所有元素相等时会退化为O(n²),可通过以下方法优化:
// 三数取中法选择基准
private static int medianOfThree(int[] array, int low, int high) {
int mid = low + (high - low) / 2;
// 对三个位置的元素排序
if (array[low] > array[mid]) swap(array, low, mid);
if (array[low] > array[high]) swap(array, low, high);
if (array[mid] > array[high]) swap(array, mid, high);
// 返回中间值的索引(mid)
return mid;
}
// 处理重复元素的三路快排
private static void quickSort3Way(int[] array, int low, int high) {
if (low >= high) return;
// 分区为 < pivot, == pivot, > pivot
int pivot = array[low];
int lt = low; // < pivot区域边界
int gt = high; // > pivot区域边界
int i = low; // 当前元素索引
while (i <= gt) {
if (array[i] < pivot) {
swap(array, lt++, i++);
} else if (array[i] > pivot) {
swap(array, i, gt--);
} else {
i++;
}
}
// 递归排序左右区域
quickSort3Way(array, low, lt - 1);
quickSort3Way(array, gt + 1, high);
}
2. 实现一个稳定的快速排序
快速排序本身不稳定,但可通过以下方法实现稳定性:
// 使用辅助数组实现稳定快速排序
public static void stableQuickSort(int[] array) {
if (array.length <= 1) return;
int pivot = array[array.length / 2];
List<Integer> left = new ArrayList<>();
List<Integer> middle = new ArrayList<>();
List<Integer> right = new ArrayList<>();
// 分区时保持原始顺序
for (int num : array) {
if (num < pivot) left.add(num);
else if (num > pivot) right.add(num);
else middle.add(num);
}
// 递归排序子数组
int[] leftArray = left.stream().mapToInt(i -> i).toArray();
int[] rightArray = right.stream().mapToInt(i -> i).toArray();
stableQuickSort(leftArray);
stableQuickSort(rightArray);
// 合并结果
int index = 0;
for (int num : leftArray) array[index++] = num;
for (int num : middle) array[index++] = num;
for (int num : rightArray) array[index++] = num;
}
3. 如何在1GB内存中排序10GB数据?
使用外部归并排序算法,步骤如下:
实现关键点:
- 使用缓冲流减少I/O操作
- 动态调整块大小以适应内存
- 采用多路归并减少归并趟数
- 利用磁盘预读和缓存优化
总结与展望
排序算法是计算机科学的基石,从简单的冒泡排序到复杂的Timsort,每种算法都有其独特的设计思想和适用场景。在实际开发中,应根据数据特性、规模和环境约束选择最合适的排序策略,而非盲目追求理论最优性能。
随着大数据和AI的发展,排序算法也在不断演进:
- 量子排序算法理论上可达到O(log n)时间复杂度
- 基于机器学习的自适应排序可动态选择最优算法
- 分布式排序系统可处理EB级数据
掌握排序算法不仅能解决实际问题,更能培养算法设计的思维方式。建议深入研究Timsort和FlashSort等现代排序算法的源码,理解其工程优化的精髓。
收藏本文,下次遇到排序问题时,你将拥有一份全面的技术指南。关注项目仓库获取更多算法实现和优化技巧!
【免费下载链接】Java All Algorithms implemented in Java 项目地址: https://gitcode.com/GitHub_Trending/ja/Java
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



