1. 前言

C++中的一维数组可以存储线性结构的数据,二维数组可以存储平面结构的数据。如班上所有学生的各科目成绩就有二个维度,学生姓名维度和科目成绩维度。

这样的表格数据可以使用二维数组进行存储。

当需要存储更多维度的数据时,可以使用多维数组

二维数组和矩阵的关系:

有些教材上,把二维数组和矩阵当成一回事,其实,两者还是有区别的。

矩阵:

  • 矩阵(Matrix)是线性数学中的概念,是一个按照长方阵列排列的复数实数集合,最早用来描述方程组的系数常数信息。
  • 因为矩阵是数学上的一个概念,要求矩阵必须是数字类型的数据。
  • 使用矩阵时,会把它当成一个整体看待。

数组:

  • 数组(Array)是计算机中的一个概念。二维数组是数组中的一种结构形式。
  • 数组除了可以存储数字型数据,也能存储非数字型数据。
  • 数组中的数据总是被当成个体来对待。

当使用计算机解决数学中与矩阵有关的问题时,可以借助二维数组。所以说,二维数组是矩阵在计算机中的数字模型

下面将了解怎么创建二维数组以及如何使用二维数组解决与矩阵有关的问题。

2. 创建二维数组

二维数组一维数组创建方式是一样的,会有 2 种创建方案:

有关数组创建的细节,可以查阅与之相关的博文。

  • 静态创建:如下创建了一个 33 列的二维数组
int nums[3][3];
  • 动态创建:动态创建的数组本质是指向指针的指针。如下语句,说明数组中保存的是指针(指向一个一维数组的地址)。
int **nums=new int*[3];

无论是静态创建还是动态创建,都可以使用下标指针两种访问方式。

访问二维数组中的数据之前,先要了解二维数组的内存模型结构。二维数组可以认为是一维数组一维数组,第一个一维数组中的每一个存储单元格中都保存着一个一维数组的地址。

Tip:静态和动态创建的数组,两者在内存的存储位置不一样,但是模型结构是一样。



使用下标访问静态数组中的数据,可以先在行上移动,然后再在列上移动。

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
//静态创建数组
int nums[5][5];
//初始化第一个位置,也就是 0 行 0 列
nums[0][0]=20;
//访问第一个位置
cout<<"第一行第一列数据:"<<nums[0][0]<<endl;
//遍历整个数组
cout<<"遍历所有数据:"<<endl;
for(int i=0; i<5; i++) {
for(int j=0; j<5; j++) {
//先赋值(值仅用来测试)
nums[i][j]=i*j;
//输出
cout<<nums[i][j]<<"\t";
}
cout<<endl;
}
return 0;
}

输出结果:

第一行第一列数据:20
遍历所有数据:
0 0 0 0 0
0 1 2 3 4
0 2 4 6 8
0 3 6 9 12
0 4 8 12 16

使用指针访问静态二维数组时。

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
int nums[5][5];
//初始化第一行,第一列
**nums=20;
//访问某一个指定位置的数据
cout<<"第一行第一列数据:"<<**nums<<endl;
//遍历整个数组
cout<<"遍历所有数据:"<<endl;
for(int i=0; i<5; i++) {
for(int j=0; j<5; j++) {
// nums+i 让行指针先移 ,确定行后, 再移动列指针。最终确定位置
*(*(nums+i)+j)=i*j;
cout<<*(*(nums+i)+j)<<"\t";
}
cout<<endl;
}
return 0;
}

动态数组创建后,系统只为第一个一维数组分配了存储空间,需要编码为一维数组的每一个存储单元格创建一个一维数组。其它的无论是下标或指针访问方式和静态数组一样。

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
//动态创建二维数组,说明一维数组中存储的是地址。
int **nums = new int *[5];
//为一维数组的每一个存储位置再创建一个一维数组(一维数组的一维数组)
for (int i = 0; i < 5; i++) {
//动态创建
nums[i] = new int[5];
}
//下标、指针访问都和静态数组一样
nums[0][0] = 5;
**nums=20;
//使用动态方案创建的数组需要显示删除
for (int i = 0 ; i < 5; ++i) {
//此处的[]不可省略
delete [] nums[i];
}
return 0;
}

3. 矩阵的基本运算

二维数组可以模拟矩阵,计算机中可以使用二维数组解决与矩阵相关的运算。

用于矩阵运算操作时,把二维数组当成一个整体,所以,运算的结果也会是一个二维数组。

3.1 加法运算

现假设有 AB 2 个矩阵。矩阵加法运算遵循下面的运算规则:

  • AB矩阵对应位置的数据进行相加。
  • 结果是一个新的矩阵 C

矩阵之间进行加法运算时,需满足以下几个要求:

  • AB 2 个矩阵的维度数据类型必须是相同的。
  • AB 2 个矩阵相加后的结果是矩阵C。此 3 个矩阵满足: A+B=B+A(A+B)+C=A+(B+C)

编码实现:

  • 初始化矩阵
#include <iostream>
using namespace std;
//矩阵 A
int **num_a=new int*[3];
//矩阵 B
int **num_b=new int*[3];
//矩阵 C
int **num_c=new int*[3];
//初始化数组
void initArrays() {
//构建二维数组
for(int i=0; i<3; i++) {
// A,B,C 都是 3 行 3 列数组
num_a[i]=new int[3];
num_b[i]=new int[3];
num_c[i]=new int[3];
}
//初始化二维数组
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
//数据仅用测试
*(*(num_a+i)+j)=i*j+4;
*(*(num_b+i)+j)=i*j+2;
}
}
}
//输出 A,B 中的数据
void outArrays() {
//输出数据
cout<<"数组 A:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
cout<<*(*(num_a+i)+j)<<"\t";
}
cout<<endl;
}
//输出数据
cout<<"数组B:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
cout<<*(*(num_b+i)+j)<<"\t";
}
cout<<endl;
}
}
  • 矩阵相加
//矩阵相加
void matrixAdd() {
cout<<"矩阵相加:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
//A和B 相同位置的数据相加,保存在C中
*(*(num_c+i)+j)=*(*(num_a+i)+j)+*(*(num_b+i)+j);
cout<<*(*(num_c+i)+j)<<"\t";
}
cout<<endl;
}
}
  • 验证矩阵的 A+B=B+A(A+B)+C=A+(B+C)特性。
//验证 A+B 是否等于 B+A。根据加法的特性,这个必然成立
void validate() {
cout<<"A+B=:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
// A + B
*(*(num_c+i)+j)=*(*(num_a+i)+j)+*(*(num_b+i)+j);
cout<<*(*(num_c+i)+j)<<"\t";
}
cout<<endl;
}
cout<<"B+A=:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
//B+A
*(*(num_c+i)+j)=*(*(num_b+i)+j)+*(*(num_a+i)+j);
cout<<*(*(num_c+i)+j)<<"\t";
}
cout<<endl;
}
}
//验证 (A+B)+C 是否等于 A+(B+C)
void validate_() {
//(A+B)+C
cout<<"(A+B)+C=:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
//因为:A + B=C 所以,(A+B)+C=C+C
cout<<num_c[i][j]+num_c[i][j]<<"\t";
}
cout<<endl;
}
//计算 B+C,且把结果保存在临时数组中
int **tmp=new int*[3];
for(int i=0; i<3; i++) {
tmp[i]=new int[3];
for(int j=0; j<3; j++) {
//B+C
tmp[i][j]=num_b[i][j]+num_c[i][j];
}
cout<<endl;
}
//再计算:A+(B+C)
cout<<"A+(B+C)=:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
//B+C
cout<<num_a[i][j]+tmp[i][j]<<"\t";
}
cout<<endl;
}
}
  • 测试:
int main(int argc, char** argv) {
initArrays();
outArrays();
//矩阵相加
matrixAdd();
//A+B
validate();
//(A+B)+C=A+(B+C)
validate_();
return 0;
}

输出结果:

数组A:
4 4 4
4 5 6
4 6 8
数组B:
2 2 2
2 3 4
2 4 6
A+B=:
6 6 6
6 8 10
6 10 14
A+B=:
6 6 6
6 8 10
6 10 14
B+A=:
6 6 6
6 8 10
6 10 14
(A+B)+C=:
12 12 12
12 16 20
12 20 28
A+(B+C)=:
12 12 12
12 16 20
12 20 28

从上述结果中,可以看出 (A+B)+C 是等于 A+(B+C)

3.2 减法运算

矩阵相减矩阵相加一样,把A、B 2 个矩阵对应位置的数字相减,最终生成一个新矩阵C。且维度和数据类型需要保持相同

三者满足数学上的减法规律:

  • A-B=C

  • A=B+C

  • A-C=B

    如下所示:

编码实现:

  • 矩阵相减函数
//矩阵相减
void matrixJian() {
cout<<"A-B=:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
//A B 相同位置数据相减,结果保存在 C中
*(*(num_c+i)+j)=*(*(num_a+i)+j)-*(*(num_b+i)+j);
//输出C
cout<<*(*(num_c+i)+j)<<"\t";
}
cout<<endl;
}
}
  • 验证A=C+B
//验证 A=B+C
void validate01() {
cout<<"B+C=:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
int tmp=*(*(num_b+i)+j)+*(*(num_c+i)+j);
cout<<tmp<<"\t";
}
cout<<endl;
}
}
  • 测试代码。
int main(int argc, char** argv) {
//初始化数组
initArrays();
//输出数组
outArrays();
//矩阵相减
matrixJian();
//验证A=B+C
validate01();
return 0;
}

输出结果:

数组A:
4 4 4
4 5 6
4 6 8
数组B:
2 2 2
2 3 4
2 4 6
A-B=:
2 2 2
2 2 2
2 2 2
B+C=:
4 4 4
4 5 6
4 6 8

3.3 数乘运算

数乘指让矩阵乘以一个数字。

数乘规则:让此数字和矩阵的每一个数字相乘,最终生成一个新的矩阵。如下图所示:

矩阵的数乘遵循如下的数学上的乘法运算规律。

  • a(bA)=b(aA)
  • a(bA)=(ab)A
  • (a+b)A=aA+bA
  • a(A+B)=aA+aB

小写 ab 代表 2 个乘数。大写 A、B代表 2 个矩阵。

编码实现数乘:

//矩阵相乘
void matrixmultiply(){
cout<<"2XA=:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
*(*(num_c+i)+j)=*(*(num_a+i)+j)*2;
cout<<*(*(num_c+i)+j)<<"\t";
}
cout<<endl;
}
}

测试:

int main(int argc, char** argv) {
//初始化数组
initArrays();
//输出数组
outArrays();
//矩阵相乘
matrixmultiply();
}

输出结果:

数组A:
4 4 4
4 5 6
4 6 8
数组B:
2 2 2
2 3 4
2 4 6
2XA=:
8 8 8
8 10 12
8 12 16

矩阵的加减法和矩阵的数乘合称为矩阵的线性运算。

3.3 转置运算

把矩阵A的行和列互相交换所产生的矩阵称为A转置矩阵,这一过程称为矩阵的转置。转置用大写字母T表示。如下图所示:

矩阵的转置遵循以下的运算规律:

  • 转置后再转置,相当于没有转置。
  • 数乘后转置和数字乘以转置后的矩阵结果一样。
  • 矩阵相乘后转置和转置后再相乘的结果一样。

编码实现:

设有一矩阵为 m×n 阶(即 m 行 n 列),第 ij 列的元素是 a(i,j),需要将该矩阵转置为 n×m阶的矩阵,使其中元素满足 b(j,i)=a(i,j)

#include <iostream>
using namespace std;
//数组A为 3 行 2 列
int **num_a=new int*[3];
//数组A转置后的结果
int **num_b=new int*[2];
//输出
void outArrays() {
//输出数据
cout<<"数组A:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<2; j++) {
cout<<*(*(num_a+i)+j)<<"\t";
}
cout<<endl;
}
//输出数据
cout<<"数组B:"<<endl;
for(int i=0; i<2; i++) {
for(int j=0; j<3; j++) {
cout<<*(*(num_b+i)+j)<<"\t";
}
cout<<endl;
}
} //初始化数组
void initArrays() {
//构建 3 行 2 列的二维数组A
for(int i=0; i<3; i++) {
num_a[i]=new int[2];
}
//构建 2 行 3 列的二维数组B
for(int i=0; i<2; i++) {
num_b[i]=new int[3];
}
//初始化二维数组A
for(int i=0; i<3; i++) {
for(int j=0; j<2; j++) {
*(*(num_a+i)+j)=i*(j+1)+4;
}
}
}
//转置数组A,转置后的数据保存在 B 中
void matrixTranspose() {
for(int i=0; i<3; i++) {
for(int j=0; j<2; j++) {
num_b[j][i]=num_a[i][j];
}
}
}
int main(int argc, char** argv) {
//初始化数组
initArrays();
//转置A
matrixTranspose();
//输出
outArrays();
return 0;
}

输出结果:

数组A:
4 4
5 6
6 8
数组B(B是的转置矩阵):
4 5 6
4 6 8

如果矩阵A和其转置矩阵B相等,则称A为对称矩阵。

3.4 共轭运算

矩阵的共轭定义为:一个2×2复数矩阵的共轭(实部不变,虚部取负)如下所示:

C++内置有complex头文件,提供有计算复数的共轭函数:

#include <iostream>
#include <complex>
using namespace std;
int main() {
complex<double> cs[2][2]= {{complex<double> (3,1),complex<double> (5,0)},
{complex<double> (2,-2),complex<double> (0,1)}
};
complex<double> cs_[2][2] ;
//原矩阵
cout<<"原矩阵"<<endl;
for(int i=0; i<2; i++) {
for(int j=0; j<2; j++) {
cout<<cs[i][j]<<"\t";
}
cout<<endl;
}
for(int i=0; i<2; i++) {
for(int j=0; j<2; j++) {
//对原矩阵中的复数进行共轭运算
cs_[i][j]= conj(cs[i][j]);
}
}
//输出原矩阵的共轭矩阵
cout<<"原矩阵的共轭矩阵:"<<endl;
for(int i=0; i<2; i++) {
for(int j=0; j<2; j++) {
cout<<cs_[i][j]<<"\t";
}
cout<<endl;
}
}

输出结果:

原矩阵
(3,1) (5,0)
(2,-2) (0,1)
原矩阵的共轭矩阵:
(3,-1) (5,-0)
(2,2) (0,-1)

3.5 共轭转置

共轭转置顾名思义,共轭后再转置。

矩阵的共轭转置定义为:,也可以写为:。或者写为

一个2×2复数矩阵的共轭转置如下所示:

3.6 乘法运算

两个矩阵的乘法仅当第一个矩阵A的列数和另一个矩阵B的行数相等时才能运算。

如果m×n矩阵An×p的矩阵B相乘,它们的乘积C是一个m×p矩阵,它的一个元素:

并将此乘积记为:C=AB

矩阵的乘法满足以下运算规律:

  • 结合律:(AB)C=A(BC)

  • 左分配律:(A+B)C=AC+BC

  • 右分配律:C(A+B)=CA+CB

矩阵乘法不满足交换律。

编码实现:

#include <iostream>
using namespace std;
//数组A 为 3 行 2 列
int **num_a=new int*[3];
//数组 B为 2行3列 数组B的行数和A数组的列数相同
int **num_b=new int*[2];
//C 保存 A 乘以 B 后的结果, 3 行 3 列
int **num_c=new int*[3];
//输出
void outArrays() {
//输出数据 3 行 2 列
cout<<"数组A:"<<endl;
for(int i=0; i<3; i++) {
for(int j=0; j<2; j++) {
cout<<*(*(num_a+i)+j)<<"\t";
}
cout<<endl;
}
//输出数据
cout<<"数组B:"<<endl;
for(int i=0; i<2; i++) {
for(int j=0; j<3; j++) {
cout<<*(*(num_b+i)+j)<<"\t";
}
cout<<endl;
}
} //初始化数组
void initArrays() {
for(int i=0; i<3; i++) {
//构建 3 行 2 列的二维数组A
num_a[i]=new int[2];
//构建 3 行 3 列的二维数组C
num_c[i]=new int[3];
}
//构建 2 行 3 列的二维数组B
for(int i=0; i<2; i++) {
num_b[i]=new int[3];
}
//初始化二维数组A
for(int i=0; i<3; i++) {
for(int j=0; j<2; j++) {
//测试数据
*(*(num_a+i)+j)=i*(j+1)+4;
}
}
//初始化二维数组 B
for(int i=0; i<2; i++) {
for(int j=0; j<3; j++) {
//测试数据
*(*(num_b+i)+j)=i*(j+2)+3;
}
}
//初始化二维数组 C
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
num_c[i][j]=0;
}
}
}
//矩阵相乘 A X B
void matrixCheng() {
// i 表示A的行号
for(int i=0; i<3; i++) {
//C表示 C 的列号
int c=0;
// k 表示 B 的列号,有多少列,乘多少次
for(int k=0; k<3; k++) {
// A 的列数和 B 的行数(两者是相等的)
for(int j=0; j<2; j++) {
//A 第一行的数据乘以 B 每一列的数据
num_c[i][c]+= num_a[i][j]*num_b[j][k];
}
c++;
}
}
cout<<"AXB="<<endl;
//输出 C
for(int i=0; i<3; i++) {
for(int j=0; j<3; j++) {
cout<<num_c[i][j]<<"\t";
}
cout<<endl;
}
} int main(int argc, char** argv) {
//初始化数组
initArrays();
//输出
outArrays();
matrixCheng();
return 0;
}

输出结果:

数组A:
4 4
5 6
6 8
数组B:
3 3 3
5 6 7
AXB=
32 36 40
45 51 57
58 66 74

4. 总结

站在数学角度,矩阵有很多特性,本文通过二维数组初窥矩阵相关问题。让大家对二维数组和矩阵有一个大致的理解。

C++ 练气期之二维数组与矩阵运算的更多相关文章

  1. [19/03/13-星期三] 数组_二维数组&冒泡排序&二分查找

    一.二维数组 多维数组可以看成以数组为元素的数组.可以有二维.三维.甚至更多维数组,但是实际开发中用的非常少.最多到二维数组(我们一般使用容器代替,二维数组用的都很少). [代码示例] import ...

  2. C++ 练气期之一文看懂字符串

    C++ 练气期之细聊字符串 1. 概念 程序不仅仅用于数字计算,现代企业级项目中更多流转着充满了烟火气的人间话语.这些话语,在计算机语言称为字符串. 从字面上理解字符串,类似于用一根竹签串起了很多字符 ...

  3. PHP 二维数组根据某个字段排序

    二维数组根据某个字段排序有两种办法,一种是通过sort自己写代码,一种是直接用array_multisort排序函数 一. 手写arraysort PHP的一维数组排序函数: sort  对数组的值按 ...

  4. 剑指Offer-【面试题03:二维数组中的查找】

    package com.cxz.question3; /* * 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序. * 请完成一个函数,输入这样的一个二维数组和 ...

  5. PHP开发笔记:二维数组根据某一项来进行排序

    比如说我们现在有一个二维数组: $arr = array( ‘d' => array(‘id' => 5, ‘name' => 1, ‘age' => 7), ‘b' => ...

  6. 剑指Offer面试题:2.二维数组中的查找

    一.题目:二维数组中的查找 题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. ...

  7. PHP 根据key 给二维数组分组

    我们经常拿到一个二维数组出来,会发现结果和自己想要的有些偏差,可能需要根据二维数组里的某个字段对数组分组.先来看以下数组, Array ( [0] => Array ( [id] => 1 ...

  8. Python学习笔记 之 递归、二维数组顺时针旋转90°、正则表达式

    递归.二维数组顺时针旋转90°.正则表达式 1.   递归算法是一种直接或间接调用自身算法的过程. 特点: 递归就是在过程或函数里调用自身 明确的递归结束条件,即递归出口 简洁,但是不提倡 递归次数多 ...

  9. 个人学习记录1:二维数组保存到cookie后再读取

    二维数组保存到cookie后再读取 var heartsArray = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0],[0,0, ...

随机推荐

  1. selenium模块无头化浏览器 设置不加载页面css、图片、js

    下面代码基于火狐浏览器,谷歌浏览器代码类似 from selenium import webdriver from selenium.webdriver.firefox.options import ...

  2. IO——字节缓冲流

    缓冲流:BufferedInputStream / BufferedOutputStream 提高IO效率,减少访问磁盘的次数 数据存储在缓冲区,调用flush将缓存区的内容写入文件中,也可以直接cl ...

  3. HMS Core 6.4.0版本发布公告

    支持转化事件回传至华为应用市场商业推广,便捷归因,实时调优: 卸载分析新增卸载前路径分析,深度剖析卸载根因. 查看详情 新增关键帧能力,通过关键帧点可实现图片.文字等位置移动.旋转等动态效果,比如文字 ...

  4. CentOS 并没有死,Rocky Linux 让其重生

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 近日,CentOS 官方发文称CentOS Stream ...

  5. 零基础学Java第五节(面向对象一)

    本篇文章是<零基础学Java>专栏的第五篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 本文章首发于公众号[编程攻略] 类与对象 在哲学体系中,可以分为主 ...

  6. Angular核心概念

    一.集成开发环境@angular/cli IE8之后才有debugger工具 2009,nodejs发布,前端Big Bang 1.1 基于NodeJS的工具链 打包工具 grunt 对js代码做合并 ...

  7. There appears to be trouble with your network connection. Retrying…

    yarn 错误There appears to be trouble with your network connection. Retrying- 原因:yarn超时 解决途径: #查看代理 yar ...

  8. [C++STL] 迭代器 iterator 的使用

    定义 迭代器是一种检查容器内元素并遍历元素的数据类型,表现的像指针. 基本声明方式 容器::iterator it = v.begin();//例:vector<int>::iterato ...

  9. CoaXPress 线缆和接插件的设计要求

    本文的原理部分内容不仅适用于CoaXPress 协议,也同样适用于其它高速信号传输情形.在高速.低干扰信号传输时,线缆和接插件的选取是非常讲究的,我们在实际应用中经常会遇到线缆原因.阻抗匹配原因导致的 ...

  10. Seata源码分析(一). AT模式底层实现

    目录 GlobalTransactionScanner 继承AbstractAutoProxyCreator 实现InitializingBean接口 写在最后 以AT为例,我们使用Seata时只需要 ...