Files
hustoj/web/doc/2.2 数组的插入与删除.md

10 KiB
Raw Blame History

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

原数组:

位置:   1    2    3    4    5    6
内容: [12] [18] [23] [31] [40] [  ]

注意:这里第 6 个位置先留空,表示数组容量足够大,可以插入新元素。

现在要把 20 插入到第 3 个位置

插入后应该变成:

位置:   1    2    3    4    5    6
内容: [12] [18] [20] [23] [31] [40]

2. 插入时,元素是怎样移动的?

要想让第 3 个位置空出来,原来第 3、4、5 个位置上的元素都要向后移动一格。

但一定要记住:

插入时,要从后往前移。

移动过程如下:

原来:   [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

原来:   [12] [18] [23] [31] [40] [  ]

错误地从前往后移:
第1步  [12] [18] [23] [23] [40] [  ]
第2步  [12] [18] [23] [23] [23] [  ]
第3步  [12] [18] [23] [23] [23] [23]

你会发现:原来的 3140 都不见了!

原因是:

  • 你先把 23 复制到了后面。
  • 后面的位置被改掉以后,再继续搬,就只能搬到已经改坏的数据。

所以插入时一定要:

从最后一个元素开始,倒着往前挪。

4. 插入的步骤口诀

可以记成一句话:

先留空,从后搬,最后放。

具体就是:

  1. 先保证数组后面有空位置。
  2. 从最后一个元素开始,依次向后移动一格。
  3. 移到目标位置后停下。
  4. 把新元素放进去。
  5. 数组长度 n 加 1。

5. 插入的课堂版 C++ 代码

下面的代码约定:

  • 数组现在有 n 个元素。
  • 要把 x 插入到第 pos 个位置。
  • pos1 开始数。
  • 数组容量足够大。
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 个位置的元素

原数组:

位置:   1    2    3    4    5
内容: [ 8] [11] [14] [17] [20]

现在要删除第 3 个位置上的 14

删除后应该变成:

位置:   1    2    3    4
内容: [ 8] [11] [17] [20]

2. 删除时,元素是怎样移动的?

第 3 个位置删掉以后,会空出一个位置。后面的元素要依次往前补。

注意:

删除时,要从前往后移。

移动过程如下:

原来:   [ 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 个位置:

原来:   [ 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 个位置上的元素。
  • pos1 开始数。
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 个位置插入,也就是直接接到数组末尾:

[12] [18] [23] [31] [40]  ->  [12] [18] [23] [31] [40] [50]

这时不需要搬动任何元素,直接放进去即可。

2. 删除最后一个元素

如果删除的是最后一个元素:

[12] [18] [23] [31] [40]  ->  [12] [18] [23] [31]

这时也不需要搬动任何元素,只要把长度减 1 就行。


八、最容易犯的 5 个错误

错误 1插入时移动方向写反

结果:后面的数据会被覆盖。

错误 2删除时移动方向写反

结果:中间的数据会丢失。

错误 3忘记更新 n

插入后忘记 n++,新元素虽然放进去了,但程序可能不把它算进去。

删除后忘记 n--,旧数据虽然应该消失了,但程序还会把它输出出来。

错误 4把“第几个位置”和“下标”搞混

记住:

  • 1 个位置,对应下标 0
  • pos 个位置,对应下标 pos - 1

错误 5插入时没有预留空间

结果:数组越界,程序可能出错。


九、一张总图记住本节内容

数组插入:先腾空位

[12] [18] [23] [31] [40] [  ]
                    ←  ←  ←
[12] [18] [20] [23] [31] [40]


数组删除:先补缺口

[ 8] [11] [14] [17] [20]
               →   →
[ 8] [11] [17] [20]

记忆口诀:

插入看后面,从后往前搬;删除看前面,从前往后搬。


十、课堂版完整示例

下面是一份把“插入”和“删除”都写进去的课堂版程序。为了简单起见,输入位置 pos 按“第几个位置”来理解,也就是从 1 开始数。

#include <bits/stdc++.h>
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 个位置,请你把每一步移动都画出来:

[3] [7] [12] [15] [  ]

2. 观察删除过程

删除下面数组的第 4 个位置,请你把每一步移动都画出来:

[5] [8] [11] [14] [20]

3. 想一想

  • 为什么插入和删除都可能需要移动很多个元素?
  • 如果总是在数组最前面插入,会不会很麻烦?

如果你能清楚地回答这两个问题,就说明你已经真正理解数组的插入和删除了。