C++ 中的矩阵介绍:以二维矩阵查找为例

在 C++ 编程中,矩阵是一种非常常见的数据结构。它本质上可以理解为一个二维表格,由若干行和若干列组成。矩阵常用于图像处理、动态规划、搜索算法、图论、数学计算等场景。

例如下面这个矩阵:

复制代码
vector<vector<int>> matrix = {
    {1, 4, 7, 11, 15},
    {2, 5, 8, 12, 19},
    {3, 6, 9, 16, 22},
    {10, 13, 14, 17, 24},
    {18, 21, 23, 26, 30}
};

可以看成一个 5 行 5 列的二维表:

复制代码
1    4    7   11   15
2    5    8   12   19
3    6    9   16   22
10  13   14   17   24
18  21   23   26   30

在 C++ 中,我们通常使用:

复制代码
vector<vector<int>>

来表示一个二维矩阵。


一、什么是矩阵?

矩阵可以理解为一个由行和列组成的数据集合。

例如:

复制代码
a[0][0]  a[0][1]  a[0][2]
a[1][0]  a[1][1]  a[1][2]
a[2][0]  a[2][1]  a[2][2]

其中:

复制代码
matrix[i][j]

表示矩阵中第 i 行第 j 列的元素。

需要注意的是,C++ 中数组下标是从 0 开始的。

也就是说:

复制代码
matrix[0][0]

表示第一行第一列的元素。

复制代码
matrix[1][2]

表示第二行第三列的元素。


二、C++ 中矩阵的常见表示方式

1. 使用二维数组表示矩阵

如果矩阵大小固定,可以使用二维数组:

复制代码
int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

访问元素:

复制代码
cout << matrix[0][1]; // 输出 2

二维数组的优点是访问速度快,结构简单。

缺点是大小通常需要提前确定,灵活性较差。


2. 使用 vector 表示矩阵

在算法题和实际开发中,更常用的是:

复制代码
vector<vector<int>> matrix;

例如:

复制代码
vector<vector<int>> matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

它的结构可以理解为:

复制代码
vector<
    vector<int>
>

也就是说,外层 vector 存储每一行,内层 vector<int> 存储这一行中的每个元素。

例如:

复制代码
matrix[0] = {1, 2, 3};
matrix[1] = {4, 5, 6};
matrix[2] = {7, 8, 9};

因此:

复制代码
matrix[1][2]

就是第 2 行第 3 列的元素,也就是 6


三、如何获取矩阵的行数和列数?

在你的代码中:

复制代码
int rows = matrix.size();
int cols = matrix[0].size();

这两行非常重要。

其中:

复制代码
matrix.size()

表示矩阵有多少行。

复制代码
matrix[0].size()

表示矩阵第一行有多少列。

例如:

复制代码
vector<vector<int>> matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

这个矩阵有 2 行 3 列。

所以:

复制代码
matrix.size();     // 2
matrix[0].size();  // 3

四、为什么要判断空矩阵?

你的代码中有这样一段:

复制代码
if (matrix.empty() || matrix[0].empty())
{
    return false;
}

这是一个非常好的习惯。

因为如果矩阵为空:

复制代码
vector<vector<int>> matrix;

此时直接访问:

复制代码
matrix[0]

会发生越界错误。

所以在访问矩阵元素之前,应该先判断矩阵是否为空。

完整判断方式如下:

复制代码
if (matrix.empty() || matrix[0].empty())
{
    return false;
}

含义是:

复制代码
matrix.empty()

判断矩阵是否没有任何行。

复制代码
matrix[0].empty()

判断第一行是否没有任何元素。


五、如何遍历一个矩阵?

最常见的方式是使用双重循环。

复制代码
for (int i = 0; i < matrix.size(); i++)
{
    for (int j = 0; j < matrix[0].size(); j++)
    {
        cout << matrix[i][j] << " ";
    }
    cout << endl;
}

外层循环控制行:

复制代码
for (int i = 0; i < rows; i++)

内层循环控制列:

复制代码
for (int j = 0; j < cols; j++)

访问元素:

复制代码
matrix[i][j]

例如矩阵:

复制代码
1 2 3
4 5 6
7 8 9

遍历顺序就是:

复制代码
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9

六、通过代码理解矩阵查找

你的代码实现的是一个非常经典的问题:
在一个行和列都按升序排列的矩阵中查找目标值。

矩阵如下:

复制代码
vector<vector<int>> matrix1 = {
    {1, 4, 7, 11, 15},
    {2, 5, 8, 12, 19},
    {3, 6, 9, 16, 22},
    {10, 13, 14, 17, 24},
    {18, 21, 23, 26, 30}
};

这个矩阵有两个特点:

每一行从左到右递增。

复制代码
1 < 4 < 7 < 11 < 15

每一列从上到下递增。

复制代码
1 < 2 < 3 < 10 < 18

所以我们可以利用这个性质来提高查找效率。


七、为什么从右上角开始查找?

你的代码中是从右上角开始查找的:

复制代码
int row = 0;
int col = cols - 1;

也就是从:

复制代码
matrix[0][cols - 1]

开始。

以示例矩阵为例,右上角元素是:

复制代码
15

1    4    7   11  [15]
2    5    8   12   19
3    6    9   16   22
10  13   14   17   24
18  21   23   26   30

为什么选择右上角?

因为右上角这个位置非常特殊:

它所在的这一行中,它是当前行最大的元素。

它所在的这一列中,它是当前列最小的元素。

所以:

如果当前值比目标值大,说明当前列下面的元素更大,更不可能是答案,只能向左移动。

如果当前值比目标值小,说明当前行左边的元素更小,更不可能是答案,只能向下移动。

这样每次都可以排除一整行或者一整列。


八、核心查找逻辑分析

核心代码如下:

复制代码
while (row < rows && col >= 0)
{
    if (matrix[row][col] == target)
    {
        return true;
    } 
    else if (matrix[row][col] > target)
    {
        col--;
    } 
    else 
    {
        row++;
    }
}

这段代码的逻辑是:

当前位置元素等于目标值,说明找到了,直接返回 true

复制代码
if (matrix[row][col] == target)
{
    return true;
}

当前位置元素大于目标值,说明当前元素太大,需要往更小的方向走,也就是向左移动。

复制代码
else if (matrix[row][col] > target)
{
    col--;
}

当前位置元素小于目标值,说明当前元素太小,需要往更大的方向走,也就是向下移动。

复制代码
else
{
    row++;
}

row 超出矩阵行数,或者 col 小于 0 时,说明已经查找完整个可能区域,仍然没有找到目标值。

复制代码
return false;

九、以 target = 5 为例分析查找过程

矩阵如下:

复制代码
1    4    7   11   15
2    5    8   12   19
3    6    9   16   22
10  13   14   17   24
18  21   23   26   30

目标值:

复制代码
target = 5;

从右上角开始:

复制代码
matrix[0][4] = 15

15 > 5,向左移动。

复制代码
matrix[0][3] = 11

11 > 5,继续向左移动。

复制代码
matrix[0][2] = 7

7 > 5,继续向左移动。

复制代码
matrix[0][1] = 4

4 < 5,向下移动。

复制代码
matrix[1][1] = 5

找到了目标值,返回 true


十、以 target = 20 为例分析查找过程

目标值:

复制代码
target = 20;

查找过程如下:

复制代码
15 < 20,向下移动
19 < 20,向下移动
22 > 20,向左移动
16 < 20,向下移动
17 < 20,向下移动
24 > 20,向左移动
14 < 20,向下移动
23 > 20,向左移动
21 > 20,向左移动
18 < 20,向下移动

最终 row 超出矩阵范围,说明矩阵中没有 20,返回 false


十一、完整代码

复制代码
#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target)
    {
        // 判断矩阵是否为空
        if (matrix.empty() || matrix[0].empty())
        {
            return false;
        }

        // 获取矩阵的行数和列数
        int rows = matrix.size();
        int cols = matrix[0].size();

        // 从右上角开始查找
        int row = 0;
        int col = cols - 1;

        // 只要当前位置还在矩阵范围内,就继续查找
        while (row < rows && col >= 0)
        {
            if (matrix[row][col] == target)
            {
                return true;
            }
            else if (matrix[row][col] > target)
            {
                // 当前元素太大,向左移动
                col--;
            }
            else
            {
                // 当前元素太小,向下移动
                row++;
            }
        }

        // 没有找到目标值
        return false;
    }
};

int main() {
    Solution sol;

    vector<vector<int>> matrix1 = {
        {1, 4, 7, 11, 15},
        {2, 5, 8, 12, 19},
        {3, 6, 9, 16, 22},
        {10, 13, 14, 17, 24},
        {18, 21, 23, 26, 30}
    };

    int target1 = 5;
    cout << "Input: target = 5" << endl;
    cout << "Output: " << (sol.searchMatrix(matrix1, target1) ? "true" : "false") << endl;

    int target2 = 20;
    cout << "Input: target = 20" << endl;
    cout << "Output: " << (sol.searchMatrix(matrix1, target2) ? "true" : "false") << endl;

    return 0;
}

十二、时间复杂度分析

这个算法不是普通的双重循环遍历。

如果使用普通遍历,需要检查每一个元素:

复制代码
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        if (matrix[i][j] == target)
        {
            return true;
        }
    }
}

这种方式的时间复杂度是:

复制代码
O(m * n)

其中 m 是行数,n 是列数。

而你的代码每次只会向左或者向下移动一步。

最多移动:

复制代码
m + n

次。

所以时间复杂度是:

复制代码
O(m + n)

空间复杂度是:

复制代码
O(1)

因为没有使用额外的数据结构。


十三、矩阵在 C++ 中的常见应用

矩阵在 C++ 算法中非常常见,尤其是在以下场景:

1. 搜索问题

例如:

复制代码
二维矩阵查找
岛屿数量
单词搜索
迷宫问题

这些问题通常需要在矩阵中进行上下左右移动。


2. 动态规划

很多动态规划问题也会使用二维矩阵,例如:

复制代码
最长公共子序列
编辑距离
最小路径和
不同路径

通常会定义:

复制代码
vector<vector<int>> dp;

用来保存状态。


3. 图像处理

图像本质上也可以看成矩阵。

例如一张灰度图可以看成:

复制代码
0   23   45
88  120  255
34  76   200

每个数字代表一个像素点的灰度值。


4. 棋盘类问题

例如:

复制代码
N 皇后
数独
扫雷
五子棋
围棋

这些问题都可以使用二维矩阵来表示棋盘状态。


十四、C++ 矩阵编程的注意事项

1. 访问前一定要判断是否为空

错误写法:

复制代码
int cols = matrix[0].size();

如果矩阵为空,这行代码会报错。

推荐写法:

复制代码
if (matrix.empty() || matrix[0].empty())
{
    return false;
}

2. 注意行和列不要写反

一般来说:

复制代码
matrix[i][j]

其中:

复制代码
i 表示行
j 表示列

行数:

复制代码
matrix.size()

列数:

复制代码
matrix[0].size()

3. 注意下标越界

矩阵下标从 0 开始。

如果一个矩阵有 rows 行,那么合法的行下标范围是:

复制代码
0 到 rows - 1

如果一个矩阵有 cols 列,那么合法的列下标范围是:

复制代码
0 到 cols - 1

所以循环条件通常写成:

复制代码
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        cout << matrix[i][j] << endl;
    }
}

而不是:

复制代码
i <= rows
j <= cols

十五、总结

矩阵是 C++ 中非常重要的数据结构,本质上就是一个二维容器。

在 C++ 中,常见的矩阵表示方式有两种:

复制代码
int matrix[100][100];

和:

复制代码
vector<vector<int>> matrix;

在算法题中,更推荐使用 vector<vector<int>>,因为它更加灵活,适合动态创建矩阵。

通过本题可以看到,如果矩阵本身具有某种有序性质,我们就可以利用这种性质优化查找过程。

从右上角开始查找的核心思想是:

复制代码
当前值太大,就向左走;
当前值太小,就向下走;
等于目标值,就返回 true。

这种方法充分利用了矩阵"每一行递增、每一列递增"的特点,将原本 O(m * n) 的暴力查找优化到了 O(m + n)

相关推荐
ECT-OS-JiuHuaShan3 小时前
存在是微分张量积,标量是参数但不可能是本质。还原论泛化,是语义劫持和以偏概全的逻辑谋杀伪科学庞氏骗局
数据库·人工智能·算法·机器学习·数学建模
CQU_JIAKE3 小时前
5.22【A】
算法
j_xxx404_3 小时前
Linux线程:从内存分页机制(Page Table/TLB/Page Fault)彻底读懂 Linux 线程本质
linux·运维·服务器·开发语言·c++·人工智能·ai
2301_789015623 小时前
C++_string增删查改模拟实现
java·开发语言·c++
学习,学习,在学习3 小时前
Qt 串口通讯架构
开发语言·c++·qt·架构·qt5
2601_957882243 小时前
多账号流量内容运营的数据归因与ROI优化:从经验驱动到算法决策的技术转型
算法·产品运营·内容运营
bug大湿3 小时前
麦阵波束算法——MVDR
算法·音频
ʚ希希ɞ ྀ3 小时前
dp反思与总结
算法·leetcode·动态规划
简简单单做算法3 小时前
基于模糊神经网络的一维时间序列预测算法matlab仿真
神经网络·算法·matlab·一维时间序列预测·模糊神经网络