一、排序算法
1.1 插入算法
🌟 一、什么是插入排序?
插入排序(Insertion Sort) 是一种简单、直观且高效的比较类排序算法,其思想来源于我们打扑克牌时整理手牌的方式:
每次从未排序部分取出一个元素,插入到已排序部分的正确位置。
名字来源:像"插入"一张新牌到已有有序序列中。

🔁 二、插入排序的工作原理
如果数据比较乱,我们默认第一张或者第一个元素的数据就是有序的,其他的都看作无序的。
再从遍历无序的数组,一一跟有序的进行从后面依次进行的比较,然后插入其中。直接完成插入排序。


💻 三、Java 基础版插入排序代码实现
java
package com.lkbhua.Algorithm.sort;
public class InsertDemo {
public static void main(String[] args) {
/*插入排序:
将0索引元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。
遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。'
N的范围:0~最大索引*/
int[] arr = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
// 1、规定无序的数据的范围从X开始
int startIndex = -1 ;
for(int i = 0; i < arr.length; i++){
if(arr[i]> arr[i+1]){
System.out.println(i); // 1
// 这样就通过代码的方式找到了无序数据的开始索引
startIndex = i + 1;
break;
// 这就能表示有序的那一组数组的最后一个索引
}
}
// 剩下的数组就是无序的
System.out.println("无序数据的开始索引:" + startIndex);
// 2、遍历从startIndex开始无序的数组中的元素
for(int i = startIndex; i < arr.length; i++){
System.out.print(arr[i]+" ");
}
System.out.println();
// 3、如何插入排序呢?
for(int i = startIndex; i < arr.length; i++){
// 记录当前要插入数据的索引 ,其实就是i
// 用另外的变量记录一下
int j = i;
while( j > 0 && arr[j] < arr[j-1]){
// 交换位置
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
j--;
}
// 修改这个索引
}
// 4、打印结果
System.out.println("排序后:");
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i]+" ");
}
}
}
📊 四、时间复杂度与空间复杂度分析
| 情况 | 时间复杂度 | 说明 |
|---|---|---|
| 最好情况 | O(n) | 数组已有序,内层循环不执行 |
| 平均情况 | O(n²) | 随机数据,平均移动 n/2 次 |
| 最坏情况 | O(n²) | 数组逆序(如 [5,4,3,2,1]),每次都要移动全部已排序元素 |
| 空间复杂度 | O(1) | 仅使用常量额外空间(原地排序) |
💡 插入排序是少数能在最好情况下达到 O(n) 的 O(n²) 排序算法!
🔍 五、稳定性分析
插入排序是 ✅ 稳定的排序算法。
为什么?
- 在比较时使用
arr[j] > key(不是>=) - 相等元素不会发生交换或移动,相对顺序保持不变
✅ 六、插入排序的优点
- 实现简单:逻辑贴近人类直觉(如理牌)
- 原地排序:空间复杂度 O(1)
- 稳定:保持相等元素顺序
- 在线算法:可以边接收数据边排序
- 对小规模或近有序数据极高效
- 最好情况 O(n)
- 实际运行常数小,比快排在小数据上更快
🌟 最关键优势 :
Java 的Arrays.sort()在底层对小数组(长度 ≤ 47)会自动切换为插入排序!
🔗 为什么 Java 的 Arrays.sort() 会用插入排序?
这是工程实践中的经典优化!
- 快排、归并在小数组上递归开销大
- 插入排序在 n ≤ 10~50 时实际运行更快
插入排序虽"简单",但它是高性能排序库的重要组成部分!
❌ 七、插入排序的缺点
- 时间复杂度高:O(n²),不适合大数据量
- 频繁移动元素:相比选择排序(只交换),插入排序需要多次赋值
- 缓存性能一般:内层循环反向遍历,局部性不如顺序访问
🔍 八、入排序 vs 冒泡排序 vs 选择排序
| 特性 | 插入排序 | 冒泡排序 | 选择排序 |
|---|---|---|---|
| 时间复杂度(最好) | O(n) | O(n)(优化后) | O(n²) |
| 时间复杂度(平均) | O(n²) | O(n²) | O(n²) |
| 稳定性 | ✅ 稳定 | ✅ 稳定 | ❌ 不稳定 |
| 交换/移动次数 | O(n²) 次移动 | O(n²) 次交换 | O(n) 次交换 |
| 适用场景 | 小数据、近有序 | 教学 | 写操作昂贵 |
| 实际应用 | ✅ 被 JDK 采用 | ❌ 无 | ❌ 无 |
💡 插入排序是三者中最实用的!
1.2 快速排序
🌟一、 递归思想
复习推文推荐:
https://blog.csdn.net/2401_84643729/article/details/154534984
🌟二、什么是快速排序?
快速排序(Quick Sort) 是由 Tony Hoare 在 1960 年提出的一种高效、原地、分治型比较排序算法 。
它是目前实际应用中最广泛使用的排序算法之一 ,也是 Java Arrays.sort() 对基本类型数组的底层实现基础。
核心思想 :
"分而治之" + "分区(Partition)"
- 选择一个元素作为 基准(pivot)
- 将数组划分为两部分:
- 左边:≤ pivot 的元素
- 右边:≥ pivot 的元素
- 递归地对左右两部分排序
🔁 三、快速排序的工作原理
首先把0索引对应的元素6拿出来当作"基准数",然后定义两个变量start和end用以记录头和尾。
随后移动end,让其与基准数进行比较,发现比基准数大,则不进行放置处理。让end进行--进行下一个数字判断,10也是,第三5比基准数小,end就停在这。接下来移动start,同理,一直找到第三个元素7,停止。最后拿着start指向的7和end指向的5进行交换,随后继续寻找start++,end--,4和9进行交换,直到start == end ;这个位置就是基准数存入的位置。拿着3和6进行交换,我们称之为基准数归位。至此第一轮结束。

💻 四、Java 基础版快速排序代码实现
java
package com.lkbhua.Algorithm.sort;
public class QuickSortDemo2 {
public static void main(String[] args) {
/*快速排序:完整代码
第一轮:以0索引的数字为基准数,确定基准数在数组中正确的位置
比基准数小的全都在左边,比基准数大的全都在右边。
后面依次类推*/
int[] arr = {6,1,2,7,9,3,4,5,10,8};
quickSort(arr, 0, arr.length - 1);
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i]+" ");
}
}
// 参数一:数组
// 参数二:左边索引
// 参数三:右边索引
public static void quickSort(int[] arr, int left, int right){
// 递归的出口
if(left >= right){
return;
}
// 定义两个变量记录左右索引
int start = left;
int end = right;
// 记录基准数
int baseNumber = arr[left];
// 利用循环找到要交换的数字
while(start != end){
// 利用end索引向左移动,找到比baseNumber小的数字
while (true){
if(end <= start || arr[end] < baseNumber){
break;
}
end--;
}
// 利用start索引向右移动,找到比baseNumber大的数字
while( true){
if(end <= start || arr[start] > baseNumber){
break;
}
start++;
}
// 把end和start索引的数字交换
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
// 当start和end相等的时候,交换start索引的数字和baseNumber
// 表示基准数在数组中的正确位置
// 基准数归位:
int temp = arr[left];
arr[left] = arr[start];
arr[start] = temp;
// 确定6左边的范围,重复刚刚的动作
quickSort(arr, left, start - 1);
// 确定6右边的范围,重复刚刚的动作
quickSort(arr, start + 1, right);
}
}
🔍五、稳定性与原地性
| 特性 | 结论 | 说明 |
|---|---|---|
| 原地排序 | ✅ 是 | 仅使用 O(1) 额外空间(不计递归栈) |
| 稳定性 | ❌ 不稳定 | 分区过程中相等元素可能被交换顺序 |
1.3 面经
📘 面试加分问题
Q1:插入排序和冒泡排序哪个更快?
A:通常插入排序更快。因为:
- 插入排序内层是移动(赋值),冒泡是交换(三次赋值)
- 插入排序对近有序数据更敏感,实际比较次数更少
Q2:插入排序能用于链表吗?
A:✅ 非常适合!
链表插入只需修改指针,无需移动元素,时间复杂度仍为 O(n²),但常数更小。
Q3:为什么插入排序是稳定的?
A:因为比较条件是
>而非>=,相等元素不会被移动,相对顺序不变。
✅ 结语
插入排序看似简单,却是理论与实践结合的典范:
- 教学上:帮助理解"构建有序序列"的思想
- 工程上:被 Java、Python 等主流语言的排序库采用
🌟 记住 :
"简单不等于无用,插入排序是高性能排序的基石之一。"
Q1:快排最坏时间复杂度是什么?如何避免?
A:最坏 O(n²),发生在每次 pivot 都是最值(如已排序数组)。
避免方法:随机化 pivot、三数取中、使用双轴快排。
Q2:快排是稳定的吗?能改成稳定吗?
A:默认不稳定。理论上可通过额外空间记录原始索引实现稳定,但会失去"原地"优势,一般不这么做。
Q3:为什么 Java 对基本类型用快排,对对象用归并/Timsort?
A:因为对象排序要求稳定性,而快排不稳定;基本类型无"相等顺序"概念,可用更快的快排。
Q4:快排和归并排序怎么选?
- 要求稳定 or 内存充足 → 归并
- 内存受限 or 追求平均速度 → 快排
1.4、四大排序算法核心特性对比表📊
原地排序(In-place Sorting) 是指:
排序过程中只使用常量级(O(1))的额外存储空间 ,即不需要创建新的数组或大量辅助结构 ,所有操作都在原数组内部通过交换或移动完成。
❌ 非原地排序(Out-of-place):
- 需要额外的数组或数据结构来辅助排序
- 空间复杂度通常为 O(n)
💡 举个例子:
1// 原地:直接在 arr 上操作
2bubbleSort(arr); // 排序后 arr 本身被修改
3
4// 非原地(伪代码):
5int[] sorted = mergeSort(arr); // 返回一个新数组,原 arr 不变
✅ 关键判断标准 :
除了输入数组外,算法是否只用了几个变量(如 temp, i, j)?如果是,就是原地排
| 特性 | 冒泡排序(Bubble Sort) | 选择排序(Selection Sort) | 插入排序(Insertion Sort) | 快速排序(Quick Sort) |
|---|---|---|---|---|
| 是否原地 | ✅ 是 | ✅ 是 | ✅ 是 | ✅ 是 |
| 空间复杂度 | O(1) | O(1) | O(1) | O(log n) ~ O(n) ⚠️ |
| 时间复杂度(最好) | O(n)(优化后) | O(n²) | O(n) | O(n log n) |
| 时间复杂度(平均) | O(n²) | O(n²) | O(n²) | O(n log n) |
| 时间复杂度(最坏) | O(n²) | O(n²) | O(n²) | O(n²)(可优化避免) |
| 稳定性 | ✅ 稳定 | ❌ 不稳定 | ✅ 稳定 | ❌ 不稳定 |
| 交换/移动次数 | 最多 O(n²) 次交换 | 最多 n-1 次交换 | 最多 O(n²) 次移动 | O(n log n) 次交换(平均) |
| 适用场景 | 教学、极小数据 | 写操作昂贵的小数据 | 小数据、近有序数据 | 大数据、通用高性能排序 |
| Java 标准库是否使用 | ❌ 否 | ❌ 否 | ✅ 是(小数组优化) | ✅ 是(基本类型主算法) |
Q1:为什么快排空间复杂度是 O(log n) 而不是 O(1)?
- 快排使用递归 ,每次递归调用都会在系统栈(call stack) 中占用空间
- 平衡情况下,递归深度为 log n → 空间 O(log n)
- 最坏情况下(如已排序数组),递归深度为 n → 空间 O(n)
✅ 但业界仍认为快排是"原地排序",因为:
- 额外空间来自递归栈,而非显式分配的数组
- 没有使用与输入规模成正比的额外堆内存(heap)
- 对比归并排序(明确需要 O(n) 的临时数组),快排更节省内存
Q2、总结:"原地"意味着什么?
| 算法 | 是否原地 | 为什么? |
|---|---|---|
| 冒泡排序 | ✅ | 仅用几个变量交换相邻元素 |
| 选择排序 | ✅ | 每轮只做一次交换,无需额外数组 |
| 插入排序 | ✅ | 元素右移腾位,不依赖外部空间 |
| 快速排序 | ✅(广义) | 无额外数组,但递归栈占用 O(log n) 空间 |
声明:
题目详细分析借鉴于通义AI
以上均来源于B站@ITheima的教学内容!!!
本人跟着视频内容学习,整理知识引用