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. 百度飞桨数据处理 API 数据格式 HWC CHW 和 PIL 图像处理之间的关系

    使用百度飞桨 API 例如:Resize Normalize,处理数据的时候. Resize:如果输入的图像是 PIL 读取的图像这个数据格式是 HWC ,Resize 就需要 HWC 格式的数据. ...

  2. JS获取Cookie失败

    项目开发日记-bug多多篇(1) 在做评论功能的时候遇到了一个很无厘头的错误,我的思路是参照点赞功能,用Ajax技术异步完成评论信息的传输,然后展示在页面上. 那么在提交评论信息的同时,要连着用户名, ...

  3. linux搭建ntp时间同步服务

    1.NTP简介 NTP(Network Time Protocol,网络时间协议)用来使计算机时间同步的一种协议.它可以使计算机对其服务器或时钟源做同步化,它可以提供高精准度的时间校正(LAN上与标准 ...

  4. SSM框架中返回的是字符串还是页面跳转的问题

    如果你在控制器前的注解是@RestController的话,将返回controller方法指定的String值,@RestController注解相当于@ResponseBody和@Controlle ...

  5. mybatis入门,CRUD,万能Map,模糊查询

    第一个Mybatis程序 核心配置文件mybatis-config.xml <?xml version="1.0" encoding="UTF-8" ?& ...

  6. 简述Web3.0

    什么是 Web 3.0 以及为什么要关心它. 为了更好地理解什么是 Web 3.0,我们需要知道什么是 Web 1.0 和 2.0. 为了不让你厌烦,这里简单的解释一下: Web 1.0 -- 信息仅 ...

  7. 【PyHacker】编写WAF指纹探测与Sqlmap相结合

    使用Python编写探测WAF指纹脚本,再结合到Sqlmap中,这样以后再探测网站时,如果识别到此WAF指纹,就会显示出来.本文属于巡安似海PyHacker系列课程   编写探测识别WAF脚本 00x ...

  8. 万字长文详解HBase读写性能优化

    一.HBase 读优化 1. HBase客户端优化 和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题: 1 ...

  9. 通过Go实现AES加密和解密工具

    本文包含如下两个内容: AES加密介绍及实现原理 Go实现AES加密和解密工具 AES加密介绍及实现原理 AES( advanced encryption standard)使用相同密钥进行加密和解密 ...

  10. Vue中mixins、extends、extend和components的作用和区别

    关于mixins:官方文档: https://cn.vuejs.org/v2/guide/mixins.html 一.components Vue.component是用来注册或获取全局组件的方法,其 ...