|
|
@@ -437,7 +437,107 @@ for (int i = 0; i < arr.length - 1; i++) {
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 六、课堂练习详解
|
|
|
+## 六、插入排序(Insertion Sort)
|
|
|
+
|
|
|
+### 1. 插入排序概述
|
|
|
+
|
|
|
+**插入排序**是三种基础排序(冒泡、选择、插入)中最实用、使用最广泛的一种 O(n²) 排序算法。它的思想非常符合人类整理东西的习惯——就像**打扑克牌时抓牌插入到手牌的正确位置**。
|
|
|
+
|
|
|
+#### 核心思想
|
|
|
+
|
|
|
+> 把数组分为"**已排序**"和"**未排序**"两部分。每次从**未排序部分**取出第 1 个元素,在**已排序部分**从后往前找到合适的位置并插入。
|
|
|
+
|
|
|
+#### 算法步骤图解
|
|
|
+
|
|
|
+以数组 `{5, 3, 8, 6, 4}` 为例,从小到大排序:
|
|
|
+
|
|
|
+```
|
|
|
+初始: [5 | 3, 8, 6, 4] 竖线左边为已排序,右边为未排序
|
|
|
+
|
|
|
+第1轮(i=1):取未排序第1个元素 current=3,与已排序部分 [5] 比较
|
|
|
+ 5 > 3 → 5 向后移 → [5, 5 | 8, 6, 4]
|
|
|
+ 找到插入位置 j+1=0,插入 current=3
|
|
|
+ → [3, 5 | 8, 6, 4] ← 3 和 5 有序
|
|
|
+
|
|
|
+第2轮(i=2):取 current=8,与已排序部分 [3, 5] 比较
|
|
|
+ 5 < 8 → 无需移动,直接插入在末尾
|
|
|
+ → [3, 5, 8 | 6, 4] ← 8 已在正确位置
|
|
|
+
|
|
|
+第3轮(i=3):取 current=6,与已排序部分 [3, 5, 8] 比较
|
|
|
+ 8 > 6 → 8 向后移 → [3, 5, 8, 8 | 4]
|
|
|
+ 5 < 6 → 停止,找到插入位置 j+1=2,插入 current=6
|
|
|
+ → [3, 5, 6, 8 | 4] ← 6 插入到 5 和 8 之间
|
|
|
+
|
|
|
+第4轮(i=4):取 current=4,与已排序部分 [3, 5, 6, 8] 比较
|
|
|
+ 8 > 4 → 8 向后移 → [3, 5, 6, 8, 8]
|
|
|
+ 6 > 4 → 6 向后移 → [3, 5, 6, 6, 8]
|
|
|
+ 5 > 4 → 5 向后移 → [3, 5, 5, 6, 8]
|
|
|
+ 3 < 4 → 停止,找到插入位置 j+1=1,插入 current=4
|
|
|
+ → [3, 4, 5, 6, 8] ✅ 排序完成
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 2. 插入排序代码实现
|
|
|
+
|
|
|
+```java
|
|
|
+import java.util.Arrays;
|
|
|
+
|
|
|
+int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
+
|
|
|
+// 从第2个元素开始,假设 arr[0] 已经是已排序部分
|
|
|
+for (int i = 1; i < arr.length; i++) {
|
|
|
+ // 获取当前要插入的元素(未排序的第1个)
|
|
|
+ int current = arr[i];
|
|
|
+ // 已排序部分的最后一个索引值
|
|
|
+ int j = i - 1;
|
|
|
+ // 从后往前扫描已排序部分,寻找插入位置
|
|
|
+ // 如果已排序的元素大于 current,就向后移动一位
|
|
|
+ while (j >= 0 && arr[j] > current) {
|
|
|
+ arr[j + 1] = arr[j];
|
|
|
+ j--;
|
|
|
+ }
|
|
|
+ // 此时 j+1 就是 current 应该插入的位置
|
|
|
+ arr[j + 1] = current;
|
|
|
+ System.out.println("第" + i + "轮插入后:" + Arrays.toString(arr));
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**代码要点**:
|
|
|
+
|
|
|
+| 部分 | 说明 |
|
|
|
+|------|------|
|
|
|
+| **外层循环** `i` | 从索引 1 开始,`i` 是当前要处理的"未排序的第 1 个元素"位置 |
|
|
|
+| **`current`** | 暂存当前要插入的元素值,防止被覆盖 |
|
|
|
+| **内层 while 循环** | 从后往前扫描已排序部分,比 `current` 大的元素逐一后移 |
|
|
|
+| **`arr[j+1] = current`** | 在找到的正确位置插入当前元素 |
|
|
|
+| **循环条件** | `j >= 0 && arr[j] > current` — 未越界且当前比较值比 `current` 大 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 3. 三种排序算法对比
|
|
|
+
|
|
|
+| 特性 | 冒泡排序 | 选择排序 | 插入排序 |
|
|
|
+|------|---------|---------|---------|
|
|
|
+| **核心思路** | 相邻比较,大的往后冒 | 每轮选最小值交换 | 将元素插入已排序区的正确位置 |
|
|
|
+| **每轮交换次数** | 多次(可能 n 次) | **最多 1 次** | 多次(元素后移) |
|
|
|
+| **平均时间复杂度** | O(n²) | O(n²) | O(n²) |
|
|
|
+| **最好时间复杂度** | O(n) — 优化版 | O(n²) | **O(n)** — 已有序 |
|
|
|
+| **最坏时间复杂度** | O(n²) | O(n²) | O(n²) |
|
|
|
+| **空间复杂度** | O(1) | O(1) | O(1) |
|
|
|
+| **稳定性** | ✅ 稳定 | ❌ 不稳定 | ✅ 稳定 |
|
|
|
+| **实用性** | 低(教学用) | 中(交换成本高时适用) | **高**(小规模数据首选) |
|
|
|
+
|
|
|
+> **三种排序的实用性排序**:插入排序 > 选择排序 > 冒泡排序
|
|
|
+>
|
|
|
+> **为什么插入排序最实用?**
|
|
|
+> - 当数据**基本有序**时,插入排序的时间复杂度可达到 **O(n)**
|
|
|
+> - 插入排序是**稳定的**,不会破坏相等元素的相对顺序
|
|
|
+> - 插入排序的代码简洁,实际运行效率在 O(n²) 级别中最高
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、课堂练习详解
|
|
|
|
|
|
### 练习 01:首尾对称交换数组元素
|
|
|
|
|
|
@@ -586,7 +686,7 @@ for (int i = 0; i < newArr.length; i++) {
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 七、课后作业讲解(7月15日作业)
|
|
|
+## 八、课后作业讲解(7月15日作业)
|
|
|
|
|
|
### 作业 07:数组元素求和
|
|
|
|
|
|
@@ -674,7 +774,7 @@ System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.lengt
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 八、随堂练习要点
|
|
|
+## 九、随堂练习要点
|
|
|
|
|
|
1. **多维数组本质**:数组的数组,二维数组的元素是一维数组的引用地址
|
|
|
2. **二维数组定义**:`int[][] arr = new int[3][2];`(3 行 2 列)
|
|
|
@@ -698,21 +798,28 @@ System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.lengt
|
|
|
20. **选择排序-minIndex**:记录最小元素的索引值,初始为 `i`
|
|
|
21. **选择排序-交换条件**:`minIndex != i` 时才交换,每轮最多交换 1 次
|
|
|
22. **选择排序 vs 冒泡排序**:选择排序每轮最多交换 1 次,冒泡排序可能交换多次
|
|
|
-23. **变量交换-临时变量法**:`tmp = a; a = b; b = tmp;`
|
|
|
-24. **变量交换-加减法**:`a = a + b; b = a - b; a = a - b;`
|
|
|
-25. **首尾交换**:利用对称性,第 i 位与第 length-1-i 位交换
|
|
|
-26. **循环右移**:从后往前逐个移动,保存最后一个元素放到开头
|
|
|
-27. **取模优化**:`k = k % arr.length` 解决 k 大于数组长度的问题
|
|
|
-28. **数组去重思路**:统计不重复个数 → 创建新数组 → 填充不重复元素
|
|
|
-29. **布尔标记法**:使用 `boolean` 变量标记元素是否重复
|
|
|
-30. **打擂法找最大值**:假设第一个是最大值,遇到更大的就更新
|
|
|
-31. **数组求和**:遍历数组,累加每个元素
|
|
|
-32. **倒序输出**:从 `length-1` 到 `0` 递减遍历
|
|
|
-33. **奇偶判断**:`n % 2 == 0` 偶,`n % 2 != 0` 奇
|
|
|
+23. **插入排序**:将未排序的第 1 个元素插入到已排序部分的正确位置
|
|
|
+24. **插入排序-分治思想**:把数组分为"已排序"和"未排序"两部分
|
|
|
+25. **插入排序-外层循环**:`i` 从 1 开始,标记当前要处理的未排序元素
|
|
|
+26. **插入排序-current**:暂存当前要插入的元素值,防止被覆盖
|
|
|
+27. **插入排序-内层 while**:从后往前扫描已排序部分,比 current 大的元素逐一后移
|
|
|
+28. **插入排序-实用性**:三种基础排序中最实用,数据基本有序时效率可达 O(n)
|
|
|
+29. **三种排序实用性排序**:插入排序 > 选择排序 > 冒泡排序
|
|
|
+30. **变量交换-临时变量法**:`tmp = a; a = b; b = tmp;`
|
|
|
+31. **变量交换-加减法**:`a = a + b; b = a - b; a = a - b;`
|
|
|
+32. **首尾交换**:利用对称性,第 i 位与第 length-1-i 位交换
|
|
|
+33. **循环右移**:从后往前逐个移动,保存最后一个元素放到开头
|
|
|
+34. **取模优化**:`k = k % arr.length` 解决 k 大于数组长度的问题
|
|
|
+35. **数组去重思路**:统计不重复个数 → 创建新数组 → 填充不重复元素
|
|
|
+36. **布尔标记法**:使用 `boolean` 变量标记元素是否重复
|
|
|
+37. **打擂法找最大值**:假设第一个是最大值,遇到更大的就更新
|
|
|
+38. **数组求和**:遍历数组,累加每个元素
|
|
|
+39. **倒序输出**:从 `length-1` 到 `0` 递减遍历
|
|
|
+40. **奇偶判断**:`n % 2 == 0` 偶,`n % 2 != 0` 奇
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 九、知识拓展
|
|
|
+## 十、知识拓展
|
|
|
|
|
|
### 1. 数组的地址值
|
|
|
|
|
|
@@ -771,7 +878,7 @@ System.out.println(a); // 输出类似:[I@b4c966a
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 十、完整代码汇总
|
|
|
+## 十一、完整代码汇总
|
|
|
|
|
|
### Demo01.java — 多维数组
|
|
|
|
|
|
@@ -1003,6 +1110,8 @@ public class Demo05 {
|
|
|
选择的思路是每一轮从剩余未排序的元素中找出最小的元素,
|
|
|
直接把它放到未排序部分的第1位。
|
|
|
核心思路:每一轮从未排序区间选出最小值,与未排序区间的第1个元素进行交换。
|
|
|
+ 优点:交换次数比冒泡更少
|
|
|
+ 缺点:不稳定,可能会破坏相对顺序
|
|
|
*/
|
|
|
public static void main(String[] args) {
|
|
|
int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
@@ -1028,6 +1137,53 @@ public class Demo05 {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+### Demo06.java — 插入排序
|
|
|
+
|
|
|
+```java
|
|
|
+import java.util.Arrays;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author WanJl
|
|
|
+ * @version 1.0
|
|
|
+ * @title Demo06
|
|
|
+ * @description 数组排序-插入排序
|
|
|
+ * @create 2026/7/16
|
|
|
+ */
|
|
|
+public class Demo06 {
|
|
|
+ /*
|
|
|
+ 插入排序
|
|
|
+ 是三种基础排序中最实用的一种,也是使用最广泛的一种O(n²)排序算法,
|
|
|
+ 他的思想非常符合人整理东西的习惯。
|
|
|
+ 把数组分为"已排序"和"未排序"两部分,每次从未排序部分取一个元素,
|
|
|
+ 插入到已排序部分的正确位置。
|
|
|
+ 就类似于打扑克时,抓牌然后插入到手中已排序好的牌中正确的位置。
|
|
|
+ 核心思想:
|
|
|
+ 把数组视为"已排序"(前部分)和未排序(后部分)。
|
|
|
+ 每次从未排序部分中拿取第1个元素,
|
|
|
+ 从后往前在已排序部分中找到合适的位置(比较)并插入
|
|
|
+ */
|
|
|
+ public static void main(String[] args) {
|
|
|
+ int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
+ //从第2个元素开始,假设 arr[0] 已经是已排序部分
|
|
|
+ for (int i = 1; i < arr.length; i++) {
|
|
|
+ //获取当前要插入的元素,未排序的第1个
|
|
|
+ int current = arr[i];
|
|
|
+ //已排序部分的最后一个索引值
|
|
|
+ int j = i - 1;
|
|
|
+ //从后往前扫描已经排序部分,寻找插入位置
|
|
|
+ //如果已排序的元素大于current,就向后移动一位
|
|
|
+ while (j >= 0 && arr[j] > current) {
|
|
|
+ arr[j + 1] = arr[j];
|
|
|
+ j--;
|
|
|
+ }
|
|
|
+ //此时 j+1 就是current应该插入到的位置
|
|
|
+ arr[j + 1] = current;
|
|
|
+ System.out.println("第" + i + "轮插入后:" + Arrays.toString(arr));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
### Exercises01.java — 首尾对称交换
|
|
|
|
|
|
```java
|
|
|
@@ -1250,7 +1406,7 @@ public class HomeWork10 {
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 十一、拓展阅读
|
|
|
+## 十二、拓展阅读
|
|
|
|
|
|
| 概念 | 说明 |
|
|
|
|------|------|
|
|
|
@@ -1279,6 +1435,16 @@ public class HomeWork10 {
|
|
|
| **选择排序-空间复杂度** | O(1),原地排序 |
|
|
|
| **选择排序-稳定性** | ❌ 不稳定(交换可能改变相等元素的相对顺序) |
|
|
|
| **冒泡 vs 选择** | 冒泡多次交换,选择最多 1 次;冒泡稳定,选择不稳定 |
|
|
|
+| **插入排序** | 将未排序的第 1 个元素插入到已排序部分的正确位置,类似打扑克理牌 |
|
|
|
+| **插入排序-分治思想** | 把数组分为"已排序"和"未排序"两部分 |
|
|
|
+| **插入排序-外层循环** | `i` 从 1 开始,标记当前要处理的未排序元素 |
|
|
|
+| **插入排序-current** | 暂存当前要插入的元素值,防止被覆盖 |
|
|
|
+| **插入排序-内层 while** | 从后往前扫描,比 current 大的元素逐一后移,找到插入位置 |
|
|
|
+| **插入排序-时间复杂度** | 平均 O(n²),最好 O(n)(数据基本有序时),最坏 O(n²) |
|
|
|
+| **插入排序-空间复杂度** | O(1),原地排序 |
|
|
|
+| **插入排序-稳定性** | ✅ 稳定 |
|
|
|
+| **插入排序-实用性** | 三种基础 O(n²) 排序中最实用,小规模数据首选 |
|
|
|
+| **三种排序对比** | 实用性:插入 > 选择 > 冒泡;稳定性:冒泡 ✅、插入 ✅、选择 ❌ |
|
|
|
| **变量交换-临时变量** | `tmp = a; a = b; b = tmp;` 最直观的交换方式 |
|
|
|
| **变量交换-加减法** | `a = a + b; b = a - b; a = a - b;` 不借助第三个变量 |
|
|
|
| **数组循环右移** | 从后往前逐位移动,保存最后一位到第一位,外层套循环控制移动次数 |
|