# 2.2 数组的插入与删除 --- ## 一、先把数组想成一排固定座位 今天我们只学两件事: - 往数组里放进一个新元素,这叫**插入**。 - 从数组里拿走一个元素,这叫**删除**。 数组可以想成教室里一排**紧紧挨着的固定座位**,每个位置都不能乱跳。 比如下面这个数组: | 位置(第几个) | 第1个 | 第2个 | 第3个 | 第4个 | 第5个 | |:---:|:---:|:---:|:---:|:---:|:---:| | 下标 | 0 | 1 | 2 | 3 | 4 | | 内容 | 12 | 18 | 23 | 31 | 40 | 这里要特别注意: - 平时说“第 1 个位置、第 2 个位置”,是按照人的习惯数的。 - 在 C++ 数组里,下标从 `0` 开始。 所以: - 第 1 个位置,对应下标 `0` - 第 3 个位置,对应下标 `2` - 第 `pos` 个位置,对应下标 `pos - 1` > 这节课讲图的时候,我们主要说“第几个位置”;写代码的时候,再换成下标。 --- ## 二、为什么数组的插入和删除要“搬家”? 数组里的元素是**连续排在一起**的。 这就像一排已经坐好的同学: - 想在中间插入一个人,就得有人挪一挪,腾出空位。 - 想删掉中间一个人,就会空出一个洞,后面的人要补上来。 所以数组的插入和删除,最关键的不是“放进去”或“拿出来”,而是: > **怎样移动其他元素,才能既不丢数据,也不弄乱顺序。** 这就是本节最重要的地方。 --- ## 三、数组插入:先腾位置,再放新元素 ### 1. 例子:在第 3 个位置插入 20 原数组: ```text 位置: 1 2 3 4 5 6 内容: [12] [18] [23] [31] [40] [ ] ``` 注意:这里第 6 个位置先留空,表示数组容量足够大,可以插入新元素。 现在要把 `20` 插入到**第 3 个位置**。 插入后应该变成: ```text 位置: 1 2 3 4 5 6 内容: [12] [18] [20] [23] [31] [40] ``` ### 2. 插入时,元素是怎样移动的? 要想让第 3 个位置空出来,原来第 3、4、5 个位置上的元素都要向后移动一格。 但一定要记住: > **插入时,要从后往前移。** 移动过程如下: ```text 原来: [12] [18] [23] [31] [40] [ ] 第1步: [12] [18] [23] [31] [40] [40] 把 40 往后移 第2步: [12] [18] [23] [31] [31] [40] 把 31 往后移 第3步: [12] [18] [23] [23] [31] [40] 把 23 往后移 第4步: [12] [18] [20] [23] [31] [40] 把 20 放进第 3 个位置 ``` ### 3. 为什么插入要从后往前移? 因为如果你从前往后移,前面的数据会把后面的数据盖掉。 来看一个错误示范。还是在第 3 个位置插入 `20`: ```text 原来: [12] [18] [23] [31] [40] [ ] 错误地从前往后移: 第1步: [12] [18] [23] [23] [40] [ ] 第2步: [12] [18] [23] [23] [23] [ ] 第3步: [12] [18] [23] [23] [23] [23] ``` 你会发现:原来的 `31` 和 `40` 都不见了! 原因是: - 你先把 `23` 复制到了后面。 - 后面的位置被改掉以后,再继续搬,就只能搬到已经改坏的数据。 所以插入时一定要: > **从最后一个元素开始,倒着往前挪。** ### 4. 插入的步骤口诀 可以记成一句话: > **先留空,从后搬,最后放。** 具体就是: 1. 先保证数组后面有空位置。 2. 从最后一个元素开始,依次向后移动一格。 3. 移到目标位置后停下。 4. 把新元素放进去。 5. 数组长度 `n` 加 1。 ### 5. 插入的课堂版 C++ 代码 下面的代码约定: - 数组现在有 `n` 个元素。 - 要把 `x` 插入到第 `pos` 个位置。 - `pos` 从 `1` 开始数。 - 数组容量足够大。 ```cpp for (int i = n; i >= pos; i--) { a[i] = a[i - 1]; } a[pos - 1] = x; n++; ``` 你可以这样理解这段循环: - `i = n` 时,把最后一个元素搬到新位置。 - `i = n - 1` 时,把倒数第二个元素往后搬。 - 一直搬到第 `pos` 个位置为止。 --- ## 四、数组删除:先补空位,再缩短长度 ### 1. 例子:删除第 3 个位置的元素 原数组: ```text 位置: 1 2 3 4 5 内容: [ 8] [11] [14] [17] [20] ``` 现在要删除第 3 个位置上的 `14`。 删除后应该变成: ```text 位置: 1 2 3 4 内容: [ 8] [11] [17] [20] ``` ### 2. 删除时,元素是怎样移动的? 第 3 个位置删掉以后,会空出一个位置。后面的元素要依次往前补。 注意: > **删除时,要从前往后移。** 移动过程如下: ```text 原来: [ 8] [11] [14] [17] [20] 第1步: [ 8] [11] [17] [17] [20] 把 17 往前移 第2步: [ 8] [11] [17] [20] [20] 把 20 往前移 最后: [ 8] [11] [17] [20] 长度减 1 ``` 最后那个多出来的 `20` 虽然还留在原来的地方,但因为数组长度已经减 1,所以它已经**不算数组的一部分了**。 ### 3. 为什么删除要从前往后移? 因为空位在前面,应该让后面的元素一个一个补上来。 如果你反过来,从后往前移,也会出问题。还是删除第 3 个位置: ```text 原来: [ 8] [11] [14] [17] [20] 错误地从后往前移: 第1步: [ 8] [11] [14] [20] [20] 第2步: [ 8] [11] [20] [20] [20] ``` 原来的 `17` 被弄丢了。 所以删除时要记住: > **从缺口的后面开始,按顺序往前补。** ### 4. 删除的步骤口诀 可以记成一句话: > **先删掉,从前搬,最后减。** 具体就是: 1. 找到要删除的位置。 2. 让后面的元素依次向前移动一格。 3. 数组长度 `n` 减 1。 ### 5. 删除的课堂版 C++ 代码 下面的代码约定: - 数组现在有 `n` 个元素。 - 删除第 `pos` 个位置上的元素。 - `pos` 从 `1` 开始数。 ```cpp for (int i = pos; i < n; i++) { a[i - 1] = a[i]; } n--; ``` 你可以这样理解这段循环: - `i = pos` 时,把后一个元素补到空位上。 - `i = pos + 1` 时,再把更后面的元素补上来。 - 一直补到最后一个元素为止。 --- ## 五、插入和删除放在一起比较 | 操作 | 会发生什么 | 正确移动方向 | 为什么 | |:---:|:---|:---:|:---| | 插入 | 中间要腾出一个空位 | 从后往前 | 先保护后面的数据,不让它们被覆盖 | | 删除 | 中间会出现一个缺口 | 从前往后 | 让后面的数据按顺序补上来 | 再记一遍: - **插入:从后往前移** - **删除:从前往后移** 这是本节最重要的结论。 --- ## 六、两个最容易忽略的小细节 ### 1. 插入前,数组要有空位 如果数组已经装满了,就不能直接插入。 比如数组最多只能放 5 个数,现在已经有 5 个数了,再插入第 6 个,就没有地方放了。 所以写程序时,常常会这样做: - 开一个更大的数组,比如最多放 `100` 个数。 - 实际已经用了多少个,用变量 `n` 记录。 ### 2. 删除后,不是把数“擦掉”,而是把长度减 1 数组删除一个元素后,最后那个位置里可能还保留着旧数值,但这已经不重要了。 因为程序只看前 `n` 个元素: - 删除前看前 `n` 个 - 删除后只看前 `n - 1` 个 所以真正关键的是: > **长度变了,数组有效范围也变了。** --- ## 七、特殊情况也要会判断 ### 1. 在最后面插入 如果要在第 `n + 1` 个位置插入,也就是直接接到数组末尾: ```text [12] [18] [23] [31] [40] -> [12] [18] [23] [31] [40] [50] ``` 这时不需要搬动任何元素,直接放进去即可。 ### 2. 删除最后一个元素 如果删除的是最后一个元素: ```text [12] [18] [23] [31] [40] -> [12] [18] [23] [31] ``` 这时也不需要搬动任何元素,只要把长度减 1 就行。 --- ## 八、最容易犯的 5 个错误 ### 错误 1:插入时移动方向写反 结果:后面的数据会被覆盖。 ### 错误 2:删除时移动方向写反 结果:中间的数据会丢失。 ### 错误 3:忘记更新 `n` 插入后忘记 `n++`,新元素虽然放进去了,但程序可能不把它算进去。 删除后忘记 `n--`,旧数据虽然应该消失了,但程序还会把它输出出来。 ### 错误 4:把“第几个位置”和“下标”搞混 记住: - 第 `1` 个位置,对应下标 `0` - 第 `pos` 个位置,对应下标 `pos - 1` ### 错误 5:插入时没有预留空间 结果:数组越界,程序可能出错。 --- ## 九、一张总图记住本节内容 ```text 数组插入:先腾空位 [12] [18] [23] [31] [40] [ ] ← ← ← [12] [18] [20] [23] [31] [40] 数组删除:先补缺口 [ 8] [11] [14] [17] [20] → → [ 8] [11] [17] [20] ``` 记忆口诀: > **插入看后面,从后往前搬;删除看前面,从前往后搬。** --- ## 十、课堂版完整示例 下面是一份把“插入”和“删除”都写进去的课堂版程序。为了简单起见,输入位置 `pos` 按“第几个位置”来理解,也就是从 `1` 开始数。 ```cpp #include using namespace std; const int MAXN = 105; int a[MAXN]; int n; void insertValue(int pos, int x) { for (int i = n; i >= pos; i--) { a[i] = a[i - 1]; } a[pos - 1] = x; n++; } void deleteValue(int pos) { for (int i = pos; i < n; i++) { a[i - 1] = a[i]; } n--; } int main() { cin >> n; for (int i = 0; i < n; i++) { cin >> a[i]; } int pos, x; cin >> pos >> x; insertValue(pos, x); for (int i = 0; i < n; i++) { cout << a[i] << " "; } cout << "\n"; cin >> pos; deleteValue(pos); for (int i = 0; i < n; i++) { cout << a[i] << " "; } cout << "\n"; return 0; } ``` --- ## 十一、自己试一试 ### 1. 观察移动过程 把 `9` 插入到下面数组的第 2 个位置,请你把每一步移动都画出来: ```text [3] [7] [12] [15] [ ] ``` ### 2. 观察删除过程 删除下面数组的第 4 个位置,请你把每一步移动都画出来: ```text [5] [8] [11] [14] [20] ``` ### 3. 想一想 - 为什么插入和删除都可能需要移动很多个元素? - 如果总是在数组最前面插入,会不会很麻烦? 如果你能清楚地回答这两个问题,就说明你已经真正理解数组的插入和删除了。