|
@@ -200,7 +200,129 @@ System.out.println(index); // 输出:10(排序后 689 在索引 10)
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 四、课堂练习详解
|
|
|
|
|
|
|
+## 四、冒泡排序(Bubble Sort)
|
|
|
|
|
+
|
|
|
|
|
+### 1. 冒泡排序概述
|
|
|
|
|
+
|
|
|
|
|
+**冒泡排序**是所有排序算法中最直观、最基础的一种。它的名字来源于排序过程就像水里的泡泡一样,大的元素会逐渐"冒"到数组的末端。
|
|
|
|
|
+
|
|
|
|
|
+#### 核心思想
|
|
|
|
|
+
|
|
|
|
|
+> 重复遍历数组,依次比较**相邻的两个元素**,如果顺序错误(如从小到大排序时前一个比后一个大)就交换。每轮遍历把当前未排序部分的最大值"冒泡"到正确位置。
|
|
|
|
|
+
|
|
|
|
|
+#### 算法步骤图解
|
|
|
|
|
+
|
|
|
|
|
+以数组 `{5, 3, 8, 6, 4}` 为例,从小到大排序:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+第1轮(i=0):将最大值 8 冒泡到最后
|
|
|
|
|
+ [5, 3, 8, 6, 4] → 比较 5>3 → 交换 → [3, 5, 8, 6, 4]
|
|
|
|
|
+ [3, 5, 8, 6, 4] → 比较 5<8 → 不交换
|
|
|
|
|
+ [3, 5, 8, 6, 4] → 比较 8>6 → 交换 → [3, 5, 6, 8, 4]
|
|
|
|
|
+ [3, 5, 6, 8, 4] → 比较 8>4 → 交换 → [3, 5, 6, 4, 8] ← 8 到位
|
|
|
|
|
+
|
|
|
|
|
+第2轮(i=1):将最大值 6 冒泡到倒数第二位
|
|
|
|
|
+ [3, 5, 6, 4, 8] → 比较 3<5 → 不交换
|
|
|
|
|
+ [3, 5, 6, 4, 8] → 比较 5<6 → 不交换
|
|
|
|
|
+ [3, 5, 6, 4, 8] → 比较 6>4 → 交换 → [3, 5, 4, 6, 8] ← 6 到位
|
|
|
|
|
+
|
|
|
|
|
+第3轮(i=2):将最大值 5 冒泡到倒数第三位
|
|
|
|
|
+ [3, 5, 4, 6, 8] → 比较 3<5 → 不交换
|
|
|
|
|
+ [3, 5, 4, 6, 8] → 比较 5>4 → 交换 → [3, 4, 5, 6, 8] ← 5 到位
|
|
|
|
|
+
|
|
|
|
|
+第4轮(i=3):将最大值 4 冒泡到倒数第四位
|
|
|
|
|
+ [3, 4, 5, 6, 8] → 比较 3<4 → 不交换 ← 4 已在正确位置
|
|
|
|
|
+
|
|
|
|
|
+排序完成 ✅
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 2. 基础版冒泡排序
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
|
|
+
|
|
|
|
|
+// 冒泡排序需要两层循环
|
|
|
|
|
+for (int i = 0; i < arr.length - 1; i++) {
|
|
|
|
|
+ // 外层循环:控制冒泡的轮数(共 arr.length-1 轮)
|
|
|
|
|
+ // 内层循环:在未排序区间 [0, arr.length-1-i] 中相邻比较
|
|
|
|
|
+ // 因为 i 后面的元素已经有序,不需要再比较了
|
|
|
|
|
+ for (int j = 0; j < arr.length - 1 - i; j++) {
|
|
|
|
|
+ if (arr[j] > arr[j + 1]) {
|
|
|
|
|
+ // 交换位置(加减法交换)
|
|
|
|
|
+ arr[j] = arr[j] + arr[j + 1];
|
|
|
|
|
+ arr[j + 1] = arr[j] - arr[j + 1];
|
|
|
|
|
+ arr[j] = arr[j] - arr[j + 1];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+System.out.println(Arrays.toString(arr));
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**代码要点**:
|
|
|
|
|
+| 部分 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| **外层循环** `i` | 控制冒泡的**轮数**,`arr.length - 1` 轮 |
|
|
|
|
|
+| **内层循环** `j` | 在未排序区间 **相邻比较**,范围 `[0, arr.length-1-i)` |
|
|
|
|
|
+| **`arr.length-1-i`** | 每轮结束后,末尾 `i` 个元素已有序,无需再比较 |
|
|
|
|
|
+| **相邻比较** | `arr[j] > arr[j+1]` 表示前大后小,需要交换 |
|
|
|
|
|
+
|
|
|
|
|
+> **为什么外层循环是 `arr.length-1` 轮?**
|
|
|
|
|
+> 因为 n 个元素的数组,最多需要 n-1 轮冒泡就可以完成排序(最后一轮只剩一个元素,无需再排)。
|
|
|
|
|
+
|
|
|
|
|
+> **为什么内层循环是 `arr.length-1-i`?**
|
|
|
|
|
+> 第 i 轮结束后,数组末尾的 i 个元素已经是有序的(每轮冒泡出一个最大值),所以内层只需要比较前 `arr.length-1-i` 个元素。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 3. 优化版冒泡排序
|
|
|
|
|
+
|
|
|
|
|
+如果某轮冒泡过程中**没有发生任何交换**,说明数组已经有序,可以**提前结束排序**。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+int[] arr2 = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
|
|
+
|
|
|
|
|
+for (int i = 0; i < arr2.length - 1; i++) {
|
|
|
|
|
+ boolean b = false; // 标记本轮是否有交换
|
|
|
|
|
+ for (int j = 0; j < arr2.length - 1 - i; j++) {
|
|
|
|
|
+ if (arr2[j] > arr2[j + 1]) {
|
|
|
|
|
+ // 交换位置
|
|
|
|
|
+ arr2[j] = arr2[j] + arr2[j + 1];
|
|
|
|
|
+ arr2[j + 1] = arr2[j] - arr2[j + 1];
|
|
|
|
|
+ arr2[j] = arr2[j] - arr2[j + 1];
|
|
|
|
|
+ b = true; // 本轮有交换
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果本轮没有发生任何交换,说明数组已经有序,提前结束
|
|
|
|
|
+ if (b == false) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println("第" + (i + 1) + "轮冒泡后:" + Arrays.toString(arr2));
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**优化点**:引入 `boolean` 标记变量,如果某一轮没有发生交换,说明数组已有序,直接 `break` 退出循环。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 4. 冒泡排序总结
|
|
|
|
|
+
|
|
|
|
|
+| 特性 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| **算法类型** | 比较排序、交换排序 |
|
|
|
|
|
+| **时间复杂度(平均)** | O(n²) |
|
|
|
|
|
+| **时间复杂度(最好)** | O(n) — 数组已有序(优化版) |
|
|
|
|
|
+| **时间复杂度(最坏)** | O(n²) — 数组逆序 |
|
|
|
|
|
+| **空间复杂度** | O(1) — 原地排序,不需要额外空间 |
|
|
|
|
|
+| **稳定性** | ✅ 稳定(相等元素的相对位置不变) |
|
|
|
|
|
+| **适用场景** | 小规模数据排序,教学演示 |
|
|
|
|
|
+
|
|
|
|
|
+> **数据结构与算法初探**:数组是最基础的**线性结构**之一,冒泡排序是最基础的**排序算法**之一。后续还会学习选择排序、插入排序、快速排序等更多算法。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 五、课堂练习详解
|
|
|
|
|
|
|
|
### 练习 01:首尾对称交换数组元素
|
|
### 练习 01:首尾对称交换数组元素
|
|
|
|
|
|
|
@@ -349,7 +471,7 @@ for (int i = 0; i < newArr.length; i++) {
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 五、课后作业讲解(7月15日作业)
|
|
|
|
|
|
|
+## 六、课后作业讲解(7月15日作业)
|
|
|
|
|
|
|
|
### 作业 07:数组元素求和
|
|
### 作业 07:数组元素求和
|
|
|
|
|
|
|
@@ -437,7 +559,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 列)
|
|
@@ -451,21 +573,25 @@ System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.lengt
|
|
|
10. **`Arrays.copyOf(arr, n)`**:拷贝数组,指定新长度,多余补默认值
|
|
10. **`Arrays.copyOf(arr, n)`**:拷贝数组,指定新长度,多余补默认值
|
|
|
11. **`Arrays.copyOfRange(arr, from, to)`**:拷贝指定范围 [from, to)
|
|
11. **`Arrays.copyOfRange(arr, from, to)`**:拷贝指定范围 [from, to)
|
|
|
12. **`Arrays.binarySearch(arr, key)`**:二分查找(必须先排序)
|
|
12. **`Arrays.binarySearch(arr, key)`**:二分查找(必须先排序)
|
|
|
-13. **变量交换-临时变量法**:`tmp = a; a = b; b = tmp;`
|
|
|
|
|
-14. **变量交换-加减法**:`a = a + b; b = a - b; a = a - b;`
|
|
|
|
|
-15. **首尾交换**:利用对称性,第 i 位与第 length-1-i 位交换
|
|
|
|
|
-16. **循环右移**:从后往前逐个移动,保存最后一个元素放到开头
|
|
|
|
|
-17. **取模优化**:`k = k % arr.length` 解决 k 大于数组长度的问题
|
|
|
|
|
-18. **数组去重思路**:统计不重复个数 → 创建新数组 → 填充不重复元素
|
|
|
|
|
-19. **布尔标记法**:使用 `boolean` 变量标记元素是否重复
|
|
|
|
|
-20. **打擂法找最大值**:假设第一个是最大值,遇到更大的就更新
|
|
|
|
|
-21. **数组求和**:遍历数组,累加每个元素
|
|
|
|
|
-22. **倒序输出**:从 `length-1` 到 `0` 递减遍历
|
|
|
|
|
-23. **奇偶判断**:`n % 2 == 0` 偶,`n % 2 != 0` 奇
|
|
|
|
|
|
|
+13. **冒泡排序**:重复遍历数组,相邻元素两两比较,大的往后冒
|
|
|
|
|
+14. **冒泡排序-外层循环**:控制冒泡轮数,共 `arr.length - 1` 轮
|
|
|
|
|
+15. **冒泡排序-内层循环**:在未排序区间 `[0, arr.length-1-i]` 相邻比较
|
|
|
|
|
+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` 奇
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 七、知识拓展
|
|
|
|
|
|
|
+## 八、知识拓展
|
|
|
|
|
|
|
|
### 1. 数组的地址值
|
|
### 1. 数组的地址值
|
|
|
|
|
|
|
@@ -524,7 +650,7 @@ System.out.println(a); // 输出类似:[I@b4c966a
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 八、完整代码汇总
|
|
|
|
|
|
|
+## 九、完整代码汇总
|
|
|
|
|
|
|
|
### Demo01.java — 多维数组
|
|
### Demo01.java — 多维数组
|
|
|
|
|
|
|
@@ -668,6 +794,74 @@ public class Demo03 {
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+### Demo04.java — 冒泡排序
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @author WanJl
|
|
|
|
|
+ * @version 1.0
|
|
|
|
|
+ * @title Demo04
|
|
|
|
|
+ * @description 数组排序-冒泡排序
|
|
|
|
|
+ * @create 2026/7/16
|
|
|
|
|
+ */
|
|
|
|
|
+public class Demo04 {
|
|
|
|
|
+ /*
|
|
|
|
|
+ 我们现在已经学了最基础的 数据结构与算法
|
|
|
|
|
+ 数据结构:数组结构--属于线性结构的一种
|
|
|
|
|
+ 算法:比如排序算法(冒泡、选择、插入...)
|
|
|
|
|
+ 冒泡排序,是所有排序【算法】最直观的,最基础的一种。
|
|
|
|
|
+ 冒泡排序就像水里的泡泡一样,大的元素会逐渐的冒到数组的末端。
|
|
|
|
|
+
|
|
|
|
|
+ 冒泡排序的核心思想:
|
|
|
|
|
+ 重复遍历数组,依次比较相邻的两个元素,如果顺序错误就交换。
|
|
|
|
|
+ 每轮遍历把当前未排序的部分的最大值"冒泡"到正确位置。
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ /*
|
|
|
|
|
+ 冒泡排序的基础版
|
|
|
|
|
+ 每一轮把未排序部分的最大值"冒泡"到数组末尾
|
|
|
|
|
+ */
|
|
|
|
|
+ int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
|
|
+ //冒泡排序需要两层循环
|
|
|
|
|
+ for (int i = 0; i < arr.length - 1; i++) { //外层循环,控制的一共冒泡多少轮
|
|
|
|
|
+ //内层循环 在未排序的区间[0,arr.length-1-i] 中相邻比较
|
|
|
|
|
+ //因为 i 后面的元素已经有序了,不需要再比较了
|
|
|
|
|
+ for (int j = 0; j < arr.length - 1 - i; j++) {
|
|
|
|
|
+ if (arr[j] > arr[j + 1]) {
|
|
|
|
|
+ //交换位置(加减法交换)
|
|
|
|
|
+ arr[j] = arr[j] + arr[j + 1];
|
|
|
|
|
+ arr[j + 1] = arr[j] - arr[j + 1];
|
|
|
|
|
+ arr[j] = arr[j] - arr[j + 1];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println(Arrays.toString(arr));
|
|
|
|
|
+
|
|
|
|
|
+ // 优化版
|
|
|
|
|
+ int[] arr2 = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
|
|
|
|
|
+ for (int i = 0; i < arr2.length - 1; i++) {
|
|
|
|
|
+ boolean b = false; //定义boolean类型的变量,用来标记本轮是否有交换
|
|
|
|
|
+ for (int j = 0; j < arr2.length - 1 - i; j++) {
|
|
|
|
|
+ if (arr2[j] > arr2[j + 1]) {
|
|
|
|
|
+ //交换位置
|
|
|
|
|
+ arr2[j] = arr2[j] + arr2[j + 1];
|
|
|
|
|
+ arr2[j + 1] = arr2[j] - arr2[j + 1];
|
|
|
|
|
+ arr2[j] = arr2[j] - arr2[j + 1];
|
|
|
|
|
+ b = true; //本轮有交换
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ //判断,如果本轮没有发生任何交换,说明数组已经有序,提前结束
|
|
|
|
|
+ if (b == false) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println("第" + (i + 1) + "轮冒泡后:" + Arrays.toString(arr2));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
### Exercises01.java — 首尾对称交换
|
|
### Exercises01.java — 首尾对称交换
|
|
|
|
|
|
|
|
```java
|
|
```java
|
|
@@ -890,7 +1084,7 @@ public class HomeWork10 {
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 九、拓展阅读
|
|
|
|
|
|
|
+## 十、拓展阅读
|
|
|
|
|
|
|
|
| 概念 | 说明 |
|
|
| 概念 | 说明 |
|
|
|
|------|------|
|
|
|------|------|
|
|
@@ -904,6 +1098,12 @@ public class HomeWork10 {
|
|
|
| **Arrays.copyOf()** | 数组拷贝,可指定新长度,扩展部分补默认值,截断部分丢弃 |
|
|
| **Arrays.copyOf()** | 数组拷贝,可指定新长度,扩展部分补默认值,截断部分丢弃 |
|
|
|
| **Arrays.copyOfRange()** | 拷贝数组指定范围 `[from, to)`,包含 from 不包含 to |
|
|
| **Arrays.copyOfRange()** | 拷贝数组指定范围 `[from, to)`,包含 from 不包含 to |
|
|
|
| **Arrays.binarySearch()** | 二分查找,数组必须先排序,返回索引值(未找到返回负数) |
|
|
| **Arrays.binarySearch()** | 二分查找,数组必须先排序,返回索引值(未找到返回负数) |
|
|
|
|
|
+| **冒泡排序** | 重复遍历数组,相邻元素两两比较,大的往后冒,每轮把最大值冒泡到末尾 |
|
|
|
|
|
+| **冒泡排序-外层循环** | 控制冒泡轮数,n 个元素最多需要 n-1 轮 |
|
|
|
|
|
+| **冒泡排序-内层循环** | 在未排序区间 `[0, arr.length-1-i]` 相邻比较,比较范围随轮数递减 |
|
|
|
|
|
+| **冒泡排序-优化** | 引入 `boolean` 标记判断本轮是否有交换,无交换则提前结束排序 |
|
|
|
|
|
+| **冒泡排序-时间复杂度** | 平均 O(n²),最好 O(n)(优化版已有序),最坏 O(n²) |
|
|
|
|
|
+| **冒泡排序-空间复杂度** | O(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;` 不借助第三个变量 |
|
|
|
| **数组循环右移** | 从后往前逐位移动,保存最后一位到第一位,外层套循环控制移动次数 |
|
|
| **数组循环右移** | 从后往前逐位移动,保存最后一位到第一位,外层套循环控制移动次数 |
|