当前位置: 首页 > news >正文

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)

http://www.jsqmd.com/news/867964/

相关文章:

  • 解密Palantir系列一:2. 传统软件的三大断裂
  • 人机这个二体问题背后往往隐藏着人机环境三体问题
  • 人机协同的五个典型特征
  • 全球眼用缓释药市场调查:预计2032年将攀升至25.46亿美元
  • Git 死亡三连实录:pull 冲突 → push 被拒 → merge 炸锅,完整抢救指南
  • 以源码方式使用pip install安装时报错ModuleNotFoundError: No module named ‘tomli‘
  • 4米2蓝牌飞翼车为啥买不到
  • C++ STL 双端队列 deque 详细介绍
  • DeepSeek商用许可迷雾破局:从MIT误读到商业闭源红线,资深IP律师揭穿3大认知幻觉
  • 行为验证码降本优势详解 从开发运维用户转化安全计费四维降低企业验证成本
  • Image2.0生成的PPT图片转换成可编辑的PPT的一种方法
  • 中国学术造假体量庞大,正在动摇Nature等全球顶刊权威
  • ARM处理器RAM接口信号解析与设计实践
  • LVS 实验搭建
  • 数据结构:4.List的认识
  • 告别检测卡点,okbiye 智能双优化破解毕业论文查重与 AI 识别难题
  • 【SOA仿真8】TMM多层膜计算器-使用说明
  • 解决Keil MDK 5.40与瑞萨FSP的hal_entry链接错误
  • 【Python】免费的中文 AI 配音方案
  • AI、二体与三体(多体)问题
  • 通风设备技术解析:从采光排烟天窗到玻璃钢风机的选型与工程实践
  • Backtracking 回溯算法
  • 第一章:Go 语言开发的大模型调用框架 - Eino
  • QQ空间说说备份终极指南:GetQzonehistory完整教程
  • SHE 密钥注入的“通配符魔法”:从 UID 通配到 AUTOSAR 分层落地
  • 新手开发者第一步从零开始调用大模型完成对话
  • 聚氨酯胶辊到底能用在哪些行业?
  • 推理框架负责人 — 学习路线 (inference-framework-learning-path)
  • 量子优化算法ITEMC:原理、实现与应用
  • 打开U盘文件夹变成.exe的问题:在MAC ios中的解决办法