本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。
点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!
系列文章目录
JAVA初阶———已更完
JAVA数据结构 DAY1-集合和时空复杂度
JAVA数据结构 DAY2-包装类和泛型
JAVA数据结构 DAY3-List接口
JAVA数据结构 DAY4-ArrayList
JAVA数据结构 DAY5-LinkedList
JAVA数据结构 DAY6-栈和队列
JAVA数据结构 DAY7-二叉树
JAVA数据结构 DAY8-堆
JAVA数据结构 DAY9 equals、Comparable、Comparator 与 PriorityQueue 深度解析
JAVA数据结构 DAY10-排序
拓展目录
手把手教你用 ArrayList 实现杨辉三角:从逻辑推导到每行代码详解
链表高频 6 题精讲 | 从入门到熟练掌握链表操作
二叉树高频题精讲 | 从入门到熟练掌握二叉树操作
二叉树高频题精讲 | 从入门到熟练掌握二叉树操作2
目录
目录
系列文章目录
拓展目录
目录
前言
前言
一、排序的核心概念与应用场景
1.1 什么是排序
1.2 排序的三大核心概念
(1)稳定性
(2)内部排序 vs 外部排序
(3)基于比较的排序 vs 非比较排序
1.3 排序的实际应用
1.4 常见的基于比较的排序算法分类
二、插入排序类:从扑克牌到高效预排序
2.1 直接插入排序
2.1.1 基本思想
2.1.2 执行流程
2.1.3 Java 代码实现
2.1.4 特性总结
2.2 希尔排序(缩小增量排序)
2.2.1 基本思想
2.2.2 增量选取规则
2.2.3 Java 代码实现
2.2.4 特性总结
三、选择排序类:选最小 / 最大元素放到指定位置
3.1 直接选择排序
3.1.1 基本思想
3.1.2 执行流程
3.1.3 Java 代码实现
3.1.4 特性总结
3.2 堆排序
3.2.1 基本思想
3.2.2 Java 代码实现
3.2.3 特性总结
四、交换排序类:通过交换元素位置实现排序
4.1 冒泡排序
4.1.1 基本思想
4.1.2 Java 代码实现
4.1.3 特性总结
4.2 快速排序
4.2.1 基本思想
4.2.2 三种分区方式
(1)挖坑法分区(最推荐,易懂)
(2)递归版快速排序
(3)非递归版快速排序
4.2.3 快速排序优化
4.2.4 特性总结
五、归并排序:分而治之,稳定高效的外部排序
5.1 基本思想
5.2 Java 代码实现
5.3 海量数据外部排序(重点)
5.4 特性总结
六、非比较类排序(了解)
6.1 计数排序
思想
适用场景
特性
6.2 基数排序
6.3 桶排序
七、七大排序算法全维度对比表(必背)
核心记忆口诀
八、Java 中的常用排序方法(开发实战)
8.1 Arrays.sort ():数组排序
8.2 Collections.sort ():集合排序
8.3 Java 对象排序:Comparable vs Comparator
九、排序算法面试真题解析(附答案)
题目 1
题目 2
题目 3
题目 4
十、全文总结与学习建议
10.1 全文核心总结
10.2 学习建议
总结
前言
小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!
前言
排序是计算机科学中最基础、最重要、应用最广泛的算法,无论是业务开发中的列表排序、搜索引擎的结果排序,还是面试中的高频考点,排序算法都是开发者必须掌握的核心技能。
在 Java 开发中,我们不仅要会用 Collections.sort()、Arrays.sort() 等内置排序方法,更要理解底层排序算法的思想、实现、性能差异与适用场景。本文将从零开始,完整讲解排序基本概念、七大基于比较的排序算法(插入排序、希尔排序、直接选择排序、堆排序、冒泡排序、快速排序、归并排序)、非比较类排序、算法复杂度与稳定性分析,并配套完整可运行的 Java 代码,最后结合 Java 集合框架讲解实际开发中的排序用法,打造一篇全栈式、一站式的 Java 排序学习指南。
一、排序的核心概念与应用场景
1.1 什么是排序
排序:将一组无序的数据记录,按照指定的关键字(如数字大小、字符串字典序、对象属性),以递增或递减的方式重新排列的过程。
简单来说,排序就是把乱序的数据变成有序的数据。例如:
- 把乱序的数字数组 [5,3,8,1,2] 变成升序 [1,2,3,5,8]
- 把商品列表按价格从低到高排列
- 把学生列表按成绩从高到低排列
1.2 排序的三大核心概念
(1)稳定性
稳定性定义:待排序序列中存在值相等的元素,排序后这些相等元素的相对前后顺序保持不变,则该排序算法是稳定的;否则为不稳定。
举个例子:原序列:[ 5(甲), 3, 5(乙) ]
- 稳定排序后:[3, 5(甲), 5(乙)](甲依然在乙前面)
- 不稳定排序后:[3, 5(乙), 5(甲)](甲和乙顺序颠倒)
稳定性意义:在多关键字排序中非常重要(如先按班级排序,再按成绩排序,稳定排序能保留班级的相对顺序)。
(2)内部排序 vs 外部排序
- 内部排序:所有待排序数据全部加载到内存中完成排序,适合数据量较小的场景。
- 外部排序:数据量过大,无法一次性加载到内存,需要在内存与磁盘 / 外部存储之间交换数据完成排序,归并排序是最常用的外部排序算法。
(3)基于比较的排序 vs 非比较排序
- 基于比较的排序:通过元素之间的两两比较确定顺序,本文重点讲解七大经典算法。
- 非比较排序:不通过比较,而是通过计数、分桶等方式排序,如计数排序、基数排序、桶排序。
1.3 排序的实际应用
排序在生活与开发中无处不在:
可以说,几乎所有复杂算法的底层,都离不开排序。
1.4 常见的基于比较的排序算法分类
我们常说的七大基于比较的排序分为四大类:
接下来,我们逐一深度讲解每一种排序的思想、代码、性能、优缺点。
二、插入排序类:从扑克牌到高效预排序
2.1 直接插入排序
2.1.1 基本思想
直接插入排序是最简单直观的排序算法,思想和我们玩扑克牌理牌完全一样:
- 把数组分为已排序区间和未排序区间
- 初始时,已排序区间只有第一个元素
- 依次将未排序区间的每个元素,插入到已排序区间的正确位置
- 重复直到所有元素插入完成
核心:逐个插入,保证前面始终有序。
2.1.2 执行流程
以数组 [3,1,4,2] 为例:
2.1.3 Java 代码实现
/**
* 直接插入排序
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
* 稳定性:稳定
*/
public static void insertSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
// 从第二个元素开始插入
for (int i = 1; i < array.length; i++) {
// 待插入元素
int tmp = array[i];
// 已排序区间最后一个元素下标
int j = i – 1;
// 向前查找插入位置,比tmp大的元素后移
for (; j >= 0 && array[j] > tmp; j–) {
array[j + 1] = array[j];
}
// 插入到正确位置
array[j + 1] = tmp;
}
}
2.1.4 特性总结
2.2 希尔排序(缩小增量排序)
2.2.1 基本思想
希尔排序是直接插入排序的高效优化版,由 Donald Shell 提出,也叫缩小增量排序。核心思想:
为什么快?gap > 1 时是预排序,让数组快速接近有序,最后 gap=1 时插入排序效率极高。
2.2.2 增量选取规则
常见增量方案:
2.2.3 Java 代码实现
/**
* 希尔排序(缩小增量排序)
* 时间复杂度:O(N^1.25) ~ O(1.6*N^1.25)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
public static void shellSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
int gap = array.length;
while (gap > 1) {
// Knuth增量:gap = gap / 3 + 1
gap = gap / 3 + 1;
// 对每组进行插入排序
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j = i – gap;
for (; j >= 0 && array[j] > tmp; j -= gap) {
array[j + gap] = array[j];
}
array[j + gap] = tmp;
}
}
}
2.2.4 特性总结
三、选择排序类:选最小 / 最大元素放到指定位置
3.1 直接选择排序
3.1.1 基本思想
直接选择排序的思想非常简单:
- 把数组分为已排序区间和未排序区间
- 每一轮从未排序区间中找到最小(或最大)元素
- 将最小元素与未排序区间的第一个元素交换
- 重复直到全部有序
核心:每一轮选一个最小的,放到前面。
3.1.2 执行流程
数组 [5,3,8,1,2]:
3.1.3 Java 代码实现
/**
* 直接选择排序
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
public static void selectSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
for (int i = 0; i < array.length – 1; i++) {
// 记录最小值下标
int minIndex = i;
// 找未排序区间最小值
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
// 交换
swap(array, i, minIndex);
}
}
// 交换数组两个元素
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
3.1.4 特性总结
3.2 堆排序
3.2.1 基本思想
堆排序是选择排序的高级版,利用二叉堆这种数据结构实现高效选择。二叉堆性质:
- 大根堆:父节点 ≥ 子节点,堆顶是最大值
- 小根堆:父节点 ≤ 子节点,堆顶是最小值
排序规则:
- 升序排序 → 建大根堆
- 降序排序 → 建小根堆
执行流程:
3.2.2 Java 代码实现
/**
* 堆排序
* 时间复杂度:O(N*logN)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
public static void heapSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
int n = array.length;
// 1. 建大根堆:从最后一个非叶子节点开始
for (int i = (n – 1 – 1) / 2; i >= 0; i–) {
siftDown(array, i, n);
}
// 2. 排序:交换堆顶与末尾元素,再调整堆
for (int i = n – 1; i > 0; i–) {
swap(array, 0, i);
siftDown(array, 0, i);
}
}
/**
* 向下调整(大根堆)
* @param array 数组
* @param parent 待调整节点
* @param length 堆的有效长度
*/
private static void siftDown(int[] array, int parent, int length) {
int child = 2 * parent + 1; // 左孩子
while (child < length) {
// 找左右孩子中较大的
if (child + 1 < length && array[child + 1] > array[child]) {
child++;
}
// 父节点比孩子大,调整结束
if (array[parent] >= array[child]) {
break;
}
// 交换父节点与孩子
swap(array, parent, child);
// 继续向下调整
parent = child;
child = 2 * parent + 1;
}
}
3.2.3 特性总结
四、交换排序类:通过交换元素位置实现排序
4.1 冒泡排序
4.1.1 基本思想
冒泡排序是最经典的交换排序,像气泡一样,大元素慢慢 “浮” 到数组末尾:
- 两两比较相邻元素
- 如果前一个 > 后一个,就交换
- 每一轮确定一个最大元素放到末尾
- 重复直到没有交换发生(数组有序)
优化:加标志位,某一轮没有交换,说明已经有序,直接退出。
4.1.2 Java 代码实现
/**
* 冒泡排序(优化版)
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
* 稳定性:稳定
*/
public static void bubbleSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
for (int i = 0; i < array.length – 1; i++) {
// 标志位:是否发生交换
boolean flag = false;
for (int j = 0; j < array.length – 1 – i; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
flag = true;
}
}
// 没有交换,提前结束
if (!flag) {
break;
}
}
}
4.1.3 特性总结
4.2 快速排序
4.2.1 基本思想
快速排序是目前综合性能最好的排序算法,也是 Java Arrays.sort() 对基础类型排序的底层算法,由 Tony Hoare 在 1962 年提出。核心思想(分治法):
一句话:选基准,分左右,递归排。
4.2.2 三种分区方式
快速排序的核心是分区(partition),常见三种实现:
(1)挖坑法分区(最推荐,易懂)
/**
* 挖坑法分区
*/
private static int partition(int[] array, int left, int right) {
// 选最左边为基准
int pivot = array[left];
int i = left;
int j = right;
while (i < j) {
// 右边找小于pivot的数,填到左边坑
while (i < j && array[j] >= pivot) {
j–;
}
array[i] = array[j];
// 左边找大于pivot的数,填到右边坑
while (i < j && array[i] <= pivot) {
i++;
}
array[j] = array[i];
}
// 基准填回坑
array[i] = pivot;
return i;
}
(2)递归版快速排序
/**
* 快速排序(递归)
* 时间复杂度:O(N*logN)
* 空间复杂度:O(logN) ~ O(N)
* 稳定性:不稳定
*/
public static void quickSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
quickSort(array, 0, array.length – 1);
}
private static void quickSort(int[] array, int left, int right) {
// 子区间长度<=1,停止递归
if (right – left <= 1) {
return;
}
// 分区
int div = partition(array, left, right);
// 递归左区间
quickSort(array, left, div);
// 递归右区间
quickSort(array, div + 1, right);
}
(3)非递归版快速排序
递归会占用栈空间,数据极大时可能栈溢出,用栈模拟递归实现非递归:
/**
* 快速排序(非递归)
*/
public static void quickSortNonR(int[] array) {
if (array == null || array.length <= 1) {
return;
}
Stack<Integer> stack = new Stack<>();
stack.push(0);
stack.push(array.length – 1);
while (!stack.isEmpty()) {
int right = stack.pop();
int left = stack.pop();
if (right – left <= 1) {
continue;
}
int div = partition(array, left, right);
// 先压右区间,再压左区间
stack.push(div + 1);
stack.push(right);
stack.push(left);
stack.push(div);
}
}
4.2.3 快速排序优化
快速排序在数据有序时会退化为 O (N^2),必须优化:
4.2.4 特性总结
五、归并排序:分而治之,稳定高效的外部排序
5.1 基本思想
归并排序是分治法的完美应用,核心是先拆分,再合并:
归并排序是唯一稳定的 O (N*logN) 排序算法,也是最常用的外部排序算法。
5.2 Java 代码实现
/**
* 归并排序
* 时间复杂度:O(N*logN)
* 空间复杂度:O(N)
* 稳定性:稳定
*/
public static void mergeSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
// 临时数组,避免频繁创建销毁
int[] tmp = new int[array.length];
mergeSort(array, 0, array.length – 1, tmp);
}
private static void mergeSort(int[] array, int left, int right, int[] tmp) {
if (left >= right) {
return;
}
int mid = left + (right – left) / 2;
// 递归拆分左
mergeSort(array, left, mid, tmp);
// 递归拆分右
mergeSort(array, mid + 1, right, tmp);
// 合并两个有序数组
merge(array, left, mid, right, tmp);
}
/**
* 合并两个有序区间 [left,mid] 和 [mid+1,right]
*/
private static void merge(int[] array, int left, int mid, int right, int[] tmp) {
int i = left; // 左区间起点
int j = mid + 1; // 右区间起点
int k = left; // 临时数组下标
// 合并
while (i <= mid && j <= right) {
if (array[i] <= array[j]) {
tmp[k++] = array[i++];
} else {
tmp[k++] = array[j++];
}
}
// 处理剩余元素
while (i <= mid) {
tmp[k++] = array[i++];
}
while (j <= right) {
tmp[k++] = array[j++];
}
// 拷贝回原数组
for (int m = left; m <= right; m++) {
array[m] = tmp[m];
}
}
5.3 海量数据外部排序(重点)
场景:内存只有 1G,待排序数据 100G,无法一次性加载到内存。归并排序解决方案:
结论:归并排序是海量数据外部排序的首选算法。
5.4 特性总结
六、非比较类排序(了解)
非比较排序不通过元素两两比较实现排序,时间复杂度可突破 O (N*logN),达到 O (N),但适用场景受限。
6.1 计数排序
思想
适用场景
- 数据范围小且集中(如年龄 0~100、考试分数 0~150)
- 整数排序
特性
- 时间复杂度:O (MAX (N, 数据范围))
- 空间复杂度:O (数据范围)
- 稳定性:稳定
6.2 基数排序
基于计数排序的扩展,按位数从低到高依次排序,适合整数、字符串排序。
6.3 桶排序
将数据分到多个有序桶,每个桶内排序,再合并所有桶,适合数据均匀分布的场景。
七、七大排序算法全维度对比表(必背)
| 冒泡排序 | 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) | O(N¹·³) | O(N²) | O(1) | 不稳定 | 插入排序 |
| 堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 | 选择排序 |
| 快速排序 | O(NlogN) | O(NlogN) | O(N²) | O(logN)~O(N) | 不稳定 | 交换排序 |
| 归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 | 归并排序 |
核心记忆口诀
八、Java 中的常用排序方法(开发实战)
在实际 Java 开发中,我们不需要手写排序算法,直接使用 JDK 内置的高效排序方法即可。
8.1 Arrays.sort ():数组排序
// 数字数组排序
int[] arr = {5,3,8,1};
Arrays.sort(arr);
// 自定义对象排序(需要实现 Comparable 或传入 Comparator)
Student[] students = new Student[2];
Arrays.sort(students); // 实现Comparable
Arrays.sort(students, new ScoreComparator()); // 传入Comparator
8.2 Collections.sort ():集合排序
对 List 集合排序,底层是归并排序,稳定:
List<Student> list = new ArrayList<>();
Collections.sort(list); // Comparable
Collections.sort(list, new ScoreComparator()); // Comparator
8.3 Java 对象排序:Comparable vs Comparator
回顾核心区别:
- Comparable:类内部实现,默认排序规则,一个规则
- Comparator:外部比较器,灵活多规则,不修改原类
这是 Java 排序开发的必考必用知识点。
九、排序算法面试真题解析(附答案)
题目 1
快速排序算法是基于 () 的一个排序算法
A: 分治法 B: 贪心法 C: 递归法 D: 动态规划法
答案:A(快排是分治法典型应用)
题目 2
对记录 (54,38,96,23,15,72,60,45,83) 直接插入排序,插入第 8 个记录 45 时比较次数
答案:C(5 次)
题目 3
占用 O (n) 辅助存储空间的是 ( )
A: 简单排序 B: 快速排序 C: 堆排序 D: 归并排序
答案:D(归并需要临时数组)
题目 4
稳定且时间复杂度 O (n²) 的是 ( )
A: 快速排序 B: 冒泡排序 C: 直接选择排序 D: 归并排序
答案:B
十、全文总结与学习建议
10.1 全文核心总结
10.2 学习建议
排序算法是编程的基石,吃透本文,你将彻底掌握 Java 排序的全部知识,无论是开发、面试、算法竞赛,都能从容应对。

总结
以上就是今天要讲的内容,本文简单记录了java数据结构,仅作为一份简单的笔记使用,大家根据注释理解,您的点赞关注收藏就是对小编最大的鼓励!
