数组教学讲义(文本图示版)


1. 数组简介

1.1 数组定义

数组(Array):一种线性表数据结构,利用一段连续的内存空间,存储一组相同类型的数据。

简而言之,「数组」 是线性表顺序存储结构的典型代表。

以整数数组为例,其存储方式如下图所示:

内存地址(低 → 高)
   ┌────┬────┬────┬────┬────┬────┐
   │ 0  │ 5  │ 2  │ 3  │ 7  │ 1  │   ...  (数组元素)
   └────┴────┴────┴────┴────┴────┘
   ↑
首地址
每个元素占相同字节,连续排列

如上图,假设数组包含 nn 个元素,每个元素都有唯一的下标索引,范围从 00 到 n−1n - 1。每个下标对应一个数据元素。

可以看出,数组在计算机中本质上是一段连续的内存区域。每个元素都占用相同大小的存储单元,这些单元都有自己的内存地址,并且在物理内存中是依次排列的。

我们可以从两个角度理解数组的定义:

  1. 线性表:线性表是一种数据元素顺序排列、类型相同的数据结构,每个元素最多只有前驱和后继两个相邻元素。数组正是线性表的一种典型实现,此外,栈、队列、链表等也属于线性表结构。
  2. 连续的内存空间:线性表有「顺序存储」和「链式存储」两种方式。顺序存储结构要求内存空间连续,相邻元素在物理内存中紧挨着。数组采用的正是顺序存储结构,且所有元素类型一致。

综合这两个角度,数组可以看作是采用「顺序存储结构」实现的「线性表」。

1.2 如何随机访问数据元素

数组最显著的特点是:支持随机访问。也就是说,可以通过下标直接定位并访问任意一个元素。

那么,计算机是如何实现通过下标高效访问数组元素的呢?

实际上,数组在内存中被分配为一段连续的空间,第一个元素的地址称为 「首地址」。每个元素都有唯一的下标和对应的内存地址。计算机在访问数组元素时,会利用下标通过 「寻址公式」 快速计算出目标元素的内存地址,从而实现高效访问。

寻址公式为:下标 ii 的元素地址 = 首地址 + ii × 单个元素占用的字节数

1.3 多维数组

前面介绍的是只有一个维度的数组,称为一维数组,其每个数据元素都通过单一的下标进行访问。但在实际应用中,许多数据具有二维或多维结构,一维数组已无法满足需求,因此引入了多维数组的概念。

以二维数组为例,其结构如下图所示:

行\列  列0  列1  列2  列3
行0   [0]  [1]  [2]  [3]
行1   [4]  [5]  [6]  [7]
行2   [8]  [9]  [10] [11]

内存中行优先存储(C++默认):
[0][1][2][3][4][5][6][7][8][9][10][11]

二维数组由 mm 行 nn 列的数据元素组成,本质上可以理解为「数组的数组」,即每个元素本身也是一个数组。第一维表示行,第二维表示列。在内存中,二维数组通常采用行优先或列优先的存储方式。

二维数组常被视为矩阵,可以用于处理如矩阵转置、矩阵加法、矩阵乘法等相关问题。在本讲义中,我们将二维数组/二维向量统一命名为 matrix。

1.4 不同编程语言中数组的实现

C++ 提供了两种主要的数组形式:

  • 传统静态数组:大小固定,在编译时确定,分配在栈或全局数据区。
  • std::vector(向量动态数组):标准库中的动态数组,大小可变,分配在堆上,支持更安全、方便的操作。

一维数组的定义

// 传统一维静态数组
int arr[1000000];                // 定义大小为 1000000 的整型数组
int arr2[5] = {1, 2, 3, 4, 5};   // 定义并初始化

// 向量动态数组(vector<int>)
vector<int> vec;                 // 空向量
vector<int> vec2 = {1, 2, 3, 4, 5};  // 列表初始化
vector<int> vec3(10, 0);         // 10 个元素,初始值均为 0

二维数组的定义

// 传统二维静态数组
int matrix[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};

// 二维向量动态数组(vector<vector<int>>)
vector<vector<int>> matrix(3, vector<int>(4, 0));  // 3 行 4 列,全 0

// 不规则二维向量(类似于 Java 的 jagged array)
vector<vector<int>> matrix;
matrix.push_back({1, 2, 3});
matrix.push_back({4, 5});
matrix.push_back({6, 7, 8, 9});

2. 数组的基本操作

数组的基本操作主要包括增、删、改、查四类。以下每种操作均给出两种 C++ 实现:

  • 方法一:使用传统静态数组 int arr[MAXN] + 维护当前长度 n,数据通过 cin 读入。
  • 方法二:使用向量动态数组 vector<int>。

注意:一维数组的操作中,向量动态数组统一使用 vector<int> 类型;二维操作中使用 vector<vector<int>> 并命名为 matrix。

2.1 访问元素

访问数组中第 indexindex 个元素:

  1. 首先检查下标 indexindex 是否在合法范围内,即 0≤index≤len(nums)−10 \le index \le len(nums) - 1,超出该范围属于非法访问。
  2. 如果下标合法,则可直接通过下标获取对应元素的值。
  3. 如果下标不合法,则抛出异常或返回特殊值。

方法一:传统静态数组(int arr[MAXN])

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000000;   // 最大容量
int arr[MAXN];              // 传统数组
int n = 0;                  // 当前实际元素个数

// 获取指定下标元素(返回是否成功,值通过引用传出)
bool getElement(int index, int& value) {
    if (0 <= index && index < n) {
        value = arr[index];
        return true;
    }
    return false;
}

int main() {
    // 输入数组长度和元素
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    // 示例输入: 7 0 5 2 3 7 1 6

    int idx, val;
    cin >> idx;   // 要访问的下标,例如 3
    if (getElement(idx, val)) {
        cout << val << endl;
    } else {
        cout << "下标越界" << endl;
    }
    return 0;
}

方法二:向量动态数组(vector)

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> vec = {0, 5, 2, 3, 7, 1, 6};

    int index = 3;
    if (index >= 0 && index < vec.size()) {
        cout << vec[index] << endl;   // 输出 3
    } else {
        cout << "下标越界" << endl;
    }
    return 0;
}

「访问数组元素」的时间复杂度为 O(1)O(1)。

2.2 查找元素

查找数组中元素值为 valval 的位置:

  1. 遍历数组,将目标值 valval 与每个元素进行比较。
  2. 找到匹配元素时返回其下标。
  3. 遍历完未找到时返回特殊值(如 −1-1)。

方法一:传统静态数组(int arr[MAXN])

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000000;
int arr[MAXN];
int n = 0;

int findElement(int val) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == val) return i;
    }
    return -1;
}

int main() {
    // 输入数组长度和元素
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    // 示例输入: 7 0 5 2 3 7 1 6

    int target;
    cin >> target;   // 要查找的值,例如 5 或 9
    cout << findElement(target) << endl;
    return 0;
}

方法二:向量动态数组(vector)

#include <bits/stdc++.h>
using namespace std;

int findElement(const vector<int>& vec, int val) {
    for (int i = 0; i < vec.size(); i++) {
        if (vec[i] == val) return i;
    }
    return -1;
}

int main() {
    vector<int> vec = {0, 5, 2, 3, 7, 1, 6};
    cout << findElement(vec, 5) << endl;   // 1
    cout << findElement(vec, 9) << endl;   // -1
    return 0;
}

线性查找的时间复杂度为 O(n)O(n)。

2.3 插入元素

在数组第 indexindex 个位置插入值 valval:

  1. 检查 indexindex 是否在 0≤index≤len(nums)0 \le index \le len(nums) 范围内。
  2. 扩展数组长度,为新元素腾出空间。
  3. 将 indexindex 及其后的元素整体向后移动一位。
  4. 在 indexindex 位置插入 valval。

插入过程示意图(假设原数组为 [0,5,2,3,7,1,6],在 index=2 插入 4):

初始:  [0, 5, 2, 3, 7, 1, 6]
           ↓ 插入位置 index=2
步骤1: 扩展长度,末尾占位 [0,5,2,3,7,1,6,_]
步骤2: 后移元素:从后往前将 index 及之后的元素右移
        [0,5,_,2,3,7,1,6]  (注意 2 被覆盖前先移走)
步骤3: 插入 4: [0,5,4,2,3,7,1,6]

方法一:传统静态数组(int arr[MAXN])

传统数组大小固定,需要预先分配足够空间(如 MAXN),并手动维护长度 n。插入时移动元素,最后长度加一。

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000000;
int arr[MAXN];
int n = 0;

bool insertElement(int index, int val) {
    if (index < 0 || index > n) return false;   // 无效位置
    if (n >= MAXN) return false;                // 空间不足
    // 将 index 及之后的元素后移
    for (int i = n; i > index; i--) {
        arr[i] = arr[i - 1];
    }
    arr[index] = val;
    n++;
    return true;
}

int main() {
    // 输入数组长度和元素
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    // 示例输入: 7 0 5 2 3 7 1 6

    int idx, val;
    cin >> idx >> val;   // 插入位置和值,例如 2 4
    bool ok = insertElement(idx, val);
    cout << "插入结果: " << (ok ? "True" : "False") << endl;
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    return 0;
}

方法二:向量动态数组(vector)

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> vec = {0, 5, 2, 3, 7, 1, 6};
    int index = 2, val = 4;

    if (index >= 0 && index <= vec.size()) {
        vec.insert(vec.begin() + index, val);
        cout << "插入结果: True" << endl;
    } else {
        cout << "插入结果: False" << endl;
    }

    for (int x : vec) cout << x << " ";  // 0 5 4 2 3 7 1 6
    cout << endl;
    return 0;
}

插入操作的时间复杂度为 O(n)O(n)。

2.4 改变元素

将数组中第 indexindex 个元素值改为 valval:

  1. 检查 indexindex 是否在 0≤index≤len(nums)−10 \le index \le len(nums) - 1 范围内。
  2. 将第 indexindex 个元素值赋值为 valval。

改变元素示意图(将 index=2 的元素改为 4):

原数组: [0, 5, 2, 3, 7, 1, 6]
              ↓
新数组: [0, 5, 4, 3, 7, 1, 6]

方法一:传统静态数组(int arr[MAXN])

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000000;
int arr[MAXN];
int n = 0;

bool changeElement(int index, int val) {
    if (0 <= index && index < n) {
        arr[index] = val;
        return true;
    }
    return false;
}

int main() {
    // 输入数组长度和元素
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    // 示例输入: 7 0 5 2 3 7 1 6

    int idx, val;
    cin >> idx >> val;   // 修改位置和新值,例如 2 4
    bool ok = changeElement(idx, val);
    cout << "修改结果: " << (ok ? "True" : "False") << endl;
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    return 0;
}

方法二:向量动态数组(vector)

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> vec = {0, 5, 2, 3, 7, 1, 6};
    int index = 2, val = 4;

    if (index >= 0 && index < vec.size()) {
        vec[index] = val;
        cout << "修改结果: True" << endl;
    } else {
        cout << "修改结果: False" << endl;
    }

    for (int x : vec) cout << x << " ";  // 0 5 4 3 7 1 6
    cout << endl;
    return 0;
}

「改变元素」的时间复杂度为 O(1)O(1)。

2.5 删除元素

删除数组中第 indexindex 个位置的元素:

  1. 检查下标 indexindex 是否在合法范围内,即 0≤index<len(nums)0 \le index < len(nums)。
  2. 将 index+1index + 1 位置及其后的元素整体向前移动一位。
  3. 删除最后一个元素(或更新数组长度)。

删除过程示意图(删除 index=2 的元素):

原数组: [0, 5, 2, 3, 7, 1, 6]
              ↓ 删除
前移后: [0, 5, 3, 7, 1, 6]

方法一:传统静态数组(int arr[MAXN])

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000000;
int arr[MAXN];
int n = 0;

bool deleteElement(int index) {
    if (index < 0 || index >= n) return false;
    // 将 index+1 及之后的元素前移
    for (int i = index; i < n - 1; i++) {
        arr[i] = arr[i + 1];
    }
    n--;
    return true;
}

int main() {
    // 输入数组长度和元素
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    // 示例输入: 7 0 5 2 3 7 1 6

    int idx;
    cin >> idx;   // 要删除的下标,例如 2
    bool ok = deleteElement(idx);
    cout << "删除结果: " << (ok ? "True" : "False") << endl;
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    return 0;
}

方法二:向量动态数组(vector)

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> vec = {0, 5, 2, 3, 7, 1, 6};
    int index = 2;

    if (index >= 0 && index < vec.size()) {
        vec.erase(vec.begin() + index);
        cout << "删除结果: True" << endl;
    } else {
        cout << "删除结果: False" << endl;
    }

    for (int x : vec) cout << x << " ";  // 0 5 3 7 1 6
    cout << endl;
    return 0;
}

删除操作的时间复杂度为 O(n)O(n)。

3. 总结

数组是一种基础且重要的数据结构,采用连续的内存空间来存储同类型的数据。其最大优势在于支持随机访问,可以通过下标高效地定位和访问任意元素。

操作 传统静态数组 (int arr[N]) 向量动态数组 (vector<int>) 时间复杂度
访问 arr[i](需手动检查边界) vec[i] 或 vec.at(i) O(1)
修改 arr[i] = val vec[i] = val
查找 线性遍历 O(n)
插入 手动移动元素,维护长度 n vec.insert()
删除 vec.erase()

推荐:在竞赛和工程开发中,若无特殊限制,应优先使用 vector<int>(向量动态数组),它更安全、方便,且提供了丰富的成员函数。

4. 练习题目

参考资料