|
@@ -322,7 +322,122 @@ for (int i = 0; i < arr2.length - 1; i++) {
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 五、课堂练习详解
|
|
|
|
|
|
|
+## 五、选择排序(Selection Sort)
|
|
|
|
|
+
|
|
|
|
|
+### 1. 选择排序概述
|
|
|
|
|
+
|
|
|
|
|
+**选择排序**的思路比冒泡排序更"聪明"——冒泡排序是不断交换相邻元素,而选择排序**每轮只做一次交换**。
|
|
|
|
|
+
|
|
|
|
|
+#### 核心思想
|
|
|
|
|
+
|
|
|
|
|
+> 每一轮从**剩余未排序的元素**中找出**最小的元素**,直接把它放到**未排序部分的第 1 位**(即已排序区间的末尾)。
|
|
|
|
|
+
|
|
|
|
|
+#### 算法步骤图解
|
|
|
|
|
+
|
|
|
|
|
+以数组 `{29, 10, 14, 37, 13}` 为例,从小到大排序:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+第1轮(i=0):在 [0, 4] 找最小值 10,与 arr[0]=29 交换
|
|
|
|
|
+ [29, 10, 14, 37, 13] → 找到最小值 10 (索引 1)
|
|
|
|
|
+ → 交换 → [10, 29, 14, 37, 13] ← 10 到位
|
|
|
|
|
+
|
|
|
|
|
+第2轮(i=1):在 [1, 4] 找最小值 13,与 arr[1]=29 交换
|
|
|
|
|
+ [10, 29, 14, 37, 13] → 找到最小值 13 (索引 4)
|
|
|
|
|
+ → 交换 → [10, 13, 14, 37, 29] ← 13 到位
|
|
|
|
|
+
|
|
|
|
|
+第3轮(i=2):在 [2, 4] 找最小值 14,已在正确位置,不交换
|
|
|
|
|
+ [10, 13, 14, 37, 29] → 找到最小值 14 (索引 2)
|
|
|
|
|
+ → minIndex == i,不交换 ← 14 已在正确位置
|
|
|
|
|
+
|
|
|
|
|
+第4轮(i=3):在 [3, 4] 找最小值 29,与 arr[3]=37 交换
|
|
|
|
|
+ [10, 13, 14, 37, 29] → 找到最小值 29 (索引 4)
|
|
|
|
|
+ → 交换 → [10, 13, 14, 29, 37] ← 29、37 到位
|
|
|
|
|
+
|
|
|
|
|
+排序完成 ✅
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 2. 选择排序代码实现
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+
|
|
|
|
|
+int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
|
|
+
|
|
|
|
|
+// 外层循环:i 既是已排序区间的末尾边界,也是当前要确定的位置
|
|
|
|
|
+for (int i = 0; i < arr.length - 1; i++) {
|
|
|
|
|
+ // 假设当前未排序区间的第 1 个元素就是最小的
|
|
|
|
|
+ int minIndex = i; // 假设 i 就是最小元素的索引值
|
|
|
|
|
+ // 内层循环:在未排序区间 [i+1, arr.length-1] 找到真正的最小值
|
|
|
|
|
+ for (int j = i + 1; j < arr.length; j++) {
|
|
|
|
|
+ if (arr[j] < arr[minIndex]) {
|
|
|
|
|
+ minIndex = j; // 更新最小元素的索引值
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果最小元素索引值 minIndex 和当前 i 不相等,就交换位置
|
|
|
|
|
+ if (minIndex != i) {
|
|
|
|
|
+ arr[minIndex] = arr[minIndex] + arr[i];
|
|
|
|
|
+ arr[i] = arr[minIndex] - arr[i];
|
|
|
|
|
+ arr[minIndex] = arr[minIndex] - arr[i];
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println("第" + (i + 1) + "轮选择后:" + Arrays.toString(arr));
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**代码要点**:
|
|
|
|
|
+
|
|
|
|
|
+| 部分 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| **外层循环** `i` | 控制选择的轮数,`arr.length - 1` 轮;`i` 也是当前要确定的位置 |
|
|
|
|
|
+| **内层循环** `j` | 在**未排序区间** `[i+1, arr.length-1]` 中寻找最小值 |
|
|
|
|
|
+| **`minIndex`** | 记录当前找到的最小元素的索引值,初始为 `i` |
|
|
|
|
|
+| **交换条件** | 当 `minIndex != i` 时,说明最小值不在当前位置,需要交换 |
|
|
|
|
|
+| **每轮只交换一次** | 找到最小值后,直接与当前位置交换,比冒泡排序更高效 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 3. 冒泡排序 vs 选择排序对比
|
|
|
|
|
+
|
|
|
|
|
+| 特性 | 冒泡排序 | 选择排序 |
|
|
|
|
|
+|------|---------|---------|
|
|
|
|
|
+| **核心思路** | 相邻比较,大的往后冒 | 每轮选最小值,放到正确位置 |
|
|
|
|
|
+| **每轮交换次数** | 多次(可能每轮交换 n 次) | **最多 1 次** |
|
|
|
|
|
+| **比较次数** | 固定 O(n²/2) | 固定 O(n²/2) |
|
|
|
|
|
+| **时间复杂度(平均)** | O(n²) | O(n²) |
|
|
|
|
|
+| **时间复杂度(最好)** | O(n) — 优化版 | O(n²) — 仍要遍历全部 |
|
|
|
|
|
+| **时间复杂度(最坏)** | O(n²) | O(n²) |
|
|
|
|
|
+| **空间复杂度** | O(1) | O(1) |
|
|
|
|
|
+| **稳定性** | ✅ 稳定 | ❌ 不稳定 |
|
|
|
|
|
+| **适用场景** | 小规模数据、教学演示 | 小规模数据、交换成本高的场景 |
|
|
|
|
|
+
|
|
|
|
|
+> **稳定性说明**:选择排序是不稳定的,因为当最小值与前面的元素交换时,可能改变相等元素的相对顺序。例如 `[5, 5, 2]` → 第一轮将 `2` 与第一个 `5` 交换,两个 `5` 的相对顺序就变了。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 4. 两种排序的过程对比
|
|
|
|
|
+
|
|
|
|
|
+以数组 `{5, 3, 8, 6, 4}` 为例:
|
|
|
|
|
+
|
|
|
|
|
+**冒泡排序**(每轮可能多次交换):
|
|
|
|
|
+```
|
|
|
|
|
+第1轮:5>3→交换, 5<8不换, 8>6→交换, 8>4→交换 → [3,5,6,4,8]
|
|
|
|
|
+第2轮:3<5不换, 5<6不换, 6>4→交换 → [3,5,4,6,8]
|
|
|
|
|
+第3轮:3<5不换, 5>4→交换 → [3,4,5,6,8]
|
|
|
|
|
+第4轮:3<4不换 → [3,4,5,6,8]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**选择排序**(每轮最多一次交换):
|
|
|
|
|
+```
|
|
|
|
|
+第1轮:找最小值 3 (索引1),与 arr[0]=5 交换 → [3,5,8,6,4]
|
|
|
|
|
+第2轮:找最小值 4 (索引4),与 arr[1]=5 交换 → [3,4,8,6,5]
|
|
|
|
|
+第3轮:找最小值 5 (索引4),与 arr[2]=8 交换 → [3,4,5,6,8]
|
|
|
|
|
+第4轮:找最小值 6 (索引3),已在正确位置不交换 → [3,4,5,6,8]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 六、课堂练习详解
|
|
|
|
|
|
|
|
### 练习 01:首尾对称交换数组元素
|
|
### 练习 01:首尾对称交换数组元素
|
|
|
|
|
|
|
@@ -471,7 +586,7 @@ for (int i = 0; i < newArr.length; i++) {
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 六、课后作业讲解(7月15日作业)
|
|
|
|
|
|
|
+## 七、课后作业讲解(7月15日作业)
|
|
|
|
|
|
|
|
### 作业 07:数组元素求和
|
|
### 作业 07:数组元素求和
|
|
|
|
|
|
|
@@ -559,7 +674,7 @@ System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.lengt
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 七、随堂练习要点
|
|
|
|
|
|
|
+## 八、随堂练习要点
|
|
|
|
|
|
|
|
1. **多维数组本质**:数组的数组,二维数组的元素是一维数组的引用地址
|
|
1. **多维数组本质**:数组的数组,二维数组的元素是一维数组的引用地址
|
|
|
2. **二维数组定义**:`int[][] arr = new int[3][2];`(3 行 2 列)
|
|
2. **二维数组定义**:`int[][] arr = new int[3][2];`(3 行 2 列)
|
|
@@ -577,21 +692,27 @@ System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.lengt
|
|
|
14. **冒泡排序-外层循环**:控制冒泡轮数,共 `arr.length - 1` 轮
|
|
14. **冒泡排序-外层循环**:控制冒泡轮数,共 `arr.length - 1` 轮
|
|
|
15. **冒泡排序-内层循环**:在未排序区间 `[0, arr.length-1-i]` 相邻比较
|
|
15. **冒泡排序-内层循环**:在未排序区间 `[0, arr.length-1-i]` 相邻比较
|
|
|
16. **冒泡排序-优化**:引入 `boolean` 标记,本轮无交换则提前结束
|
|
16. **冒泡排序-优化**:引入 `boolean` 标记,本轮无交换则提前结束
|
|
|
-18. **变量交换-临时变量法**:`tmp = a; a = b; b = tmp;`
|
|
|
|
|
-19. **变量交换-加减法**:`a = a + b; b = a - b; a = a - b;`
|
|
|
|
|
-20. **首尾交换**:利用对称性,第 i 位与第 length-1-i 位交换
|
|
|
|
|
-21. **循环右移**:从后往前逐个移动,保存最后一个元素放到开头
|
|
|
|
|
-22. **取模优化**:`k = k % arr.length` 解决 k 大于数组长度的问题
|
|
|
|
|
-23. **数组去重思路**:统计不重复个数 → 创建新数组 → 填充不重复元素
|
|
|
|
|
-24. **布尔标记法**:使用 `boolean` 变量标记元素是否重复
|
|
|
|
|
-25. **打擂法找最大值**:假设第一个是最大值,遇到更大的就更新
|
|
|
|
|
-26. **数组求和**:遍历数组,累加每个元素
|
|
|
|
|
-27. **倒序输出**:从 `length-1` 到 `0` 递减遍历
|
|
|
|
|
-28. **奇偶判断**:`n % 2 == 0` 偶,`n % 2 != 0` 奇
|
|
|
|
|
|
|
+17. **选择排序**:每轮从未排序区间选出最小值,与未排序区间的第 1 个元素交换
|
|
|
|
|
+18. **选择排序-外层循环**:`i` 是当前要确定的位置,范围 `[0, arr.length-2]`
|
|
|
|
|
+19. **选择排序-内层循环**:在未排序区间 `[i+1, arr.length-1]` 找最小值索引
|
|
|
|
|
+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` 奇
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 八、知识拓展
|
|
|
|
|
|
|
+## 九、知识拓展
|
|
|
|
|
|
|
|
### 1. 数组的地址值
|
|
### 1. 数组的地址值
|
|
|
|
|
|
|
@@ -650,7 +771,7 @@ System.out.println(a); // 输出类似:[I@b4c966a
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 九、完整代码汇总
|
|
|
|
|
|
|
+## 十、完整代码汇总
|
|
|
|
|
|
|
|
### Demo01.java — 多维数组
|
|
### Demo01.java — 多维数组
|
|
|
|
|
|
|
@@ -862,6 +983,51 @@ public class Demo04 {
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+### Demo05.java — 选择排序
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @author WanJl
|
|
|
|
|
+ * @version 1.0
|
|
|
|
|
+ * @title Demo05
|
|
|
|
|
+ * @description 数组排序-选择排序
|
|
|
|
|
+ * @create 2026/7/16
|
|
|
|
|
+ */
|
|
|
|
|
+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};
|
|
|
|
|
+ //外层循环:i 既是已排序区间的末尾边界,也是当前要确定的位置
|
|
|
|
|
+ for (int i = 0; i < arr.length - 1; i++) {
|
|
|
|
|
+ //假设当前未排序区间的第1个元素就是最小的
|
|
|
|
|
+ int minIndex = i;
|
|
|
|
|
+ //内层循环:在未排序区间 [i+1, arr.length-1] 找到真正的最小值
|
|
|
|
|
+ for (int j = i + 1; j < arr.length; j++) {
|
|
|
|
|
+ if (arr[j] < arr[minIndex]) {
|
|
|
|
|
+ minIndex = j; //更新最小元素的索引值
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ //如果最小元素索引值和当前 i 不相等,就交换位置
|
|
|
|
|
+ if (minIndex != i) {
|
|
|
|
|
+ arr[minIndex] = arr[minIndex] + arr[i];
|
|
|
|
|
+ arr[i] = arr[minIndex] - arr[i];
|
|
|
|
|
+ arr[minIndex] = arr[minIndex] - arr[i];
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println("第" + (i + 1) + "轮选择后:" + Arrays.toString(arr));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
### Exercises01.java — 首尾对称交换
|
|
### Exercises01.java — 首尾对称交换
|
|
|
|
|
|
|
|
```java
|
|
```java
|
|
@@ -1084,7 +1250,7 @@ public class HomeWork10 {
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 十、拓展阅读
|
|
|
|
|
|
|
+## 十一、拓展阅读
|
|
|
|
|
|
|
|
| 概念 | 说明 |
|
|
| 概念 | 说明 |
|
|
|
|------|------|
|
|
|------|------|
|
|
@@ -1104,6 +1270,15 @@ public class HomeWork10 {
|
|
|
| **冒泡排序-优化** | 引入 `boolean` 标记判断本轮是否有交换,无交换则提前结束排序 |
|
|
| **冒泡排序-优化** | 引入 `boolean` 标记判断本轮是否有交换,无交换则提前结束排序 |
|
|
|
| **冒泡排序-时间复杂度** | 平均 O(n²),最好 O(n)(优化版已有序),最坏 O(n²) |
|
|
| **冒泡排序-时间复杂度** | 平均 O(n²),最好 O(n)(优化版已有序),最坏 O(n²) |
|
|
|
| **冒泡排序-空间复杂度** | O(1),原地排序,不需要额外空间 |
|
|
| **冒泡排序-空间复杂度** | O(1),原地排序,不需要额外空间 |
|
|
|
|
|
+| **选择排序** | 每轮从未排序区间选出最小值,与未排序区间的第 1 个元素交换,每轮最多 1 次交换 |
|
|
|
|
|
+| **选择排序-外层循环** | `i` 是当前要确定的位置,范围 `[0, arr.length-2]` |
|
|
|
|
|
+| **选择排序-内层循环** | 在未排序区间 `[i+1, arr.length-1]` 找最小值索引 |
|
|
|
|
|
+| **选择排序-minIndex** | 记录最小元素的索引值,初始为当前位置 `i`,遇更小值则更新 |
|
|
|
|
|
+| **选择排序-交换条件** | `minIndex != i` 时才执行交换,每轮最多交换 1 次 |
|
|
|
|
|
+| **选择排序-时间复杂度** | 平均 O(n²),最好 O(n²),最坏 O(n²) — 始终要遍历全部 |
|
|
|
|
|
+| **选择排序-空间复杂度** | O(1),原地排序 |
|
|
|
|
|
+| **选择排序-稳定性** | ❌ 不稳定(交换可能改变相等元素的相对顺序) |
|
|
|
|
|
+| **冒泡 vs 选择** | 冒泡多次交换,选择最多 1 次;冒泡稳定,选择不稳定 |
|
|
|
| **变量交换-临时变量** | `tmp = a; a = b; b = tmp;` 最直观的交换方式 |
|
|
| **变量交换-临时变量** | `tmp = a; a = b; b = tmp;` 最直观的交换方式 |
|
|
|
| **变量交换-加减法** | `a = a + b; b = a - b; a = a - b;` 不借助第三个变量 |
|
|
| **变量交换-加减法** | `a = a + b; b = a - b; a = a - b;` 不借助第三个变量 |
|
|
|
| **数组循环右移** | 从后往前逐位移动,保存最后一位到第一位,外层套循环控制移动次数 |
|
|
| **数组循环右移** | 从后往前逐位移动,保存最后一位到第一位,外层套循环控制移动次数 |
|