MATLAB 学习笔记——2. 矩阵
概述
MATLAB 对于矩阵的支持非常好,以矩阵运算为代表的基本运算功能一直是 MATLAB 引以为自豪的核心与基础。我们可以把向量和矩阵都视作矩阵进行统一的操作。在下文中我们默认讨论二维矩阵,但 MATLAB 支持多维矩阵。行向量即行数为 1 的矩阵,列向量即列数为 1 的矩阵。
在内存中,MATLAB 使用列主序进行连续存储,与 Fortran 相同,与 C 语言是反的。在下标的使用中,MATLAB 默认下标从 1 开始,与 Fortran 相同,与 C 语言等绝大部分编程语言都不同。
矩阵的尺寸信息可以通过下面的语句获取:
size(A)
行数和列数,返回一个1x2的行向量;size(A,1)
行数;size(A,2)
列数。
注意:
- MATLAB 对于矩阵的支持是非常彻底的,甚至标量都是被视作 1 行 1 列的矩阵。
- MATLAB
的很多内置函数对于行/列向量有特殊处理,在语义上并不能自然地推广到矩阵,可能是为了用户的便利,也可能是历史兼容性。例如不建议通过
length
获取尺寸信息,为了兼容行列向量的长度语义,这个函数只会返回矩阵的行数和列数之间的最大值。 - 下面默认矩阵是由浮点数组成的二维数组,MATLAB也支持更高维度的数组(但是部分矩阵运算不支持高维数组),而且元素类型除了浮点数,还可以是字符、布尔值、结构体数组、元胞数组等。
矩阵字面量
矩阵可以通过字面值创建:
- 空格或者逗号视作单行元素的分隔;
- 分号视作不同行元素的分隔,也可以输入回车进行换行。(输入分号加上回车也只是一次换行效果,并不会叠加)
例如
1 | >> [1 2 3 4] |
(如果尺寸不合法则会报错,MATLAB 不允许长短不一致的矩阵构造)
创建特殊矩阵
可以使用特殊矩阵的构造函数:(注意:单个参数时总是默认方阵)
- 全零矩阵
zeros(m,n)
创造 m 行 n 列的全零矩阵;zeros(m)
创造 m 行 m 列的全零方阵;zeros(size(A))
创造与 A 尺寸相同的全零方阵;
- 全 1 矩阵
ones(m,n)
创造 m 行 n 列的全 1 矩阵;ones(m)
创造 m 行 m 列的全 1 方阵;ones(size(A))
创造与 A 尺寸相同的全 1 方阵;
- 随机矩阵:(每一个元素为0-1的随机数)
rand(m,n)
创造 m 行 n 列的随机矩阵;rand(m)
创造 m 行 m 列的随机方阵;
- 单位矩阵
eye(m)
创造 m 行 m 列的单位方阵;eye(m,n)
创造 m 行 n 列的单位矩阵;(只有主对角线为 1,其余均为 0)eye(size(A))
创造与 A 尺寸相同的单位矩阵;
冒号表达式用于产生等差数列(行向量),使用冒号表达式可以避免循环,计算效率更高
a:b:c
首项为a
,公差为b
,尾项由c
确定但不一定包含。
1 | >> 1:2:10 |
a:b
相当于把中间的默认步长b=1
省略。
与之类似的是linspace
函数:
linspace(a,b,n)
基于闭区间生成行向量,第一个值是 a,最后一个值是 b,中间为等差数列,一共 n 个数;- 缺省 n 时,默认取 n=100。
拼接矩阵
我们可以很方便地把两个矩阵拼起来,包括水平拼接和竖直拼接,它们分别要求两个矩阵有相同的行数或列数。
1 | >> a=[1 2];b=[3 4] |
矩阵的基础运算
矩阵可以直接和数相加减,默认会逐个元素进行。
1 | >> zeros(2,3)+2 |
可以对矩阵执行内置函数,默认会逐个元素执行。
1 | >> a=[1 2 3;4 5 6];sin(a) |
矩阵的两种转置:共轭转置A'
,转置A.'
。(对于实数矩阵两者一样)
矩阵乘法:A*B
执行标准的矩阵乘法。
1 | >> A=[1 1;2 2];B=[1 2;3 4];A*B |
两个矩阵进行逐个元素的乘法:A.*B
1 | >> A=[1 1;2 2];B=[1 2;3 4];A.*B |
同理,还有逐个元素的乘方.^
。
尤其注意,这里的运算例如+
或.*
对于尺寸兼容的 A
和 B 也是可以的,
对于矩阵的常用运算还包括:
inv(A)
求逆rank(A)
秩det(A)
行列式exp(A)
逐元素指数
注意:
- 这几个函数对大规模稠密矩阵不建议使用,因为数值算法的效率很低,而且效果通常并不太好,而且很多情况下并不需要。
exp(A)
是对矩阵的元素逐个计算exp
,矩阵的指数运算是expm(A)
。
矩阵的兼容和隐式扩展
两个矩阵称为兼容的,如果它们的尺寸分别为 (m1,m2,m3)
和
(n1,n2,n3)
,总是可以加 1 使得描述它们尺寸的整数个数一致。
要求要么 m1=n1
,要么其中一个为 1,此时维度为 1
的那个矩阵可以通过在这个维度上的简单复制达到相同的尺寸,对于
m2
与 n2
,m3
与 n3
也同理。
对尺寸不同,但是兼容的两个矩阵所进行的二元运算,可能会自动触发隐式扩展,例如
1 | A=[1 2 3]; % 行向量 |
它们做逐个元素的乘法时,会自动对尺寸进行扩展,在这个例子中就是都变成尺寸
(3,4)
的矩阵,在扩展的维度上只是单纯的复制
1 | A-> |
然后进行运算,计算A+B
和A.*B
得到的结果分别为
1 | A+B = |
注意:
- 行向量和列向量的乘法是向量乘法,但是逐元素乘法会得到矩阵。
- 列向量和行向量的乘法因为隐式扩展,完全等效于逐元素乘法,两者都会得到矩阵。
扩充矩阵
MATLAB的矩阵尺寸不是固定的,甚至允许在赋值时直接扩充矩阵尺寸(当然这会产生很多内存拷贝),例如扩充向量
1
2
3
4
5a = [];
a(1) = 1;
a(2) = 2;
a % [1 2]
扩充矩阵 1
2
3
4
5
6
7
8b = zeros(2,2);
b(2,:) = [2 2];
b(3,:) = [3 3];
b %
% 0 0
% 2 2
% 3 3
扩充后对于没有提供值的部分会自动补0。
不建议通过赋值直接扩充矩阵,这会带来不必要的内存拷贝,尤其不建议在 for 循环中扩充矩阵,因为效率非常低。
更改矩阵形状
MATLAB 提供 reshape 函数用来更改矩阵的尺寸(基于数据在内存中的列优先顺序),但是并不会修改原矩阵,而是返回一个指定尺寸的新矩阵。
与扩充尺寸必然带来的内存拷贝不同,由于MATLAB采用了懒拷贝机制,如果后续不对新矩阵中的元素进行修改,那么修改尺寸可能并不会产生额外的拷贝开销,相当于 C++ 中的引用传递。
支持两种写法,第一种是直接提供完整的目标尺寸向量(必须和原始矩阵的数据长度相匹配,否则报错),例如
1
2A = 1:10;
B = reshape(A,[5,2])
可以很方便地搭配size
函数使用,例如 1
2C = [1 2 3 4 5 6 7 8 9 10];
D = reshape(C,size(B))
第二种则是分别提供每一个维度的长度,此时可以使用一个[]
缺省,MATLAB会自动计算缺少的维度长度,例如
1
2A = 1:10;
B = reshape(A,[],2) % size = [5,2]
注意:
- reshape 和 resize 这两个名称在不同语言中经常混淆;
- 较新版本的 MATLAB 也提供了 resize 函数,但是作用只是用来在尾部加0或移除元素,也就是只考虑实际数据长度进行截断或延长,通常不需要。
矩阵的线性索引
无论是矩阵还是退化的行向量列向量,或者高维矩阵,每一个元素都可以使用一个正整数进行索引,称为线性索引。
与之不同的是,在 C/C++ 和 Numpy 中,二维数组通常是通过一维数组的数组实现的,因此对二维数组用一个整数索引会得到一个一维数组。
线性索引从 1 开始,完全对应于矩阵在内存中列主序的排列顺序。(在同一列中,相邻的元素就在内存中相邻)
1 | >> a=[1 2 3; 4 5 6] |
支持基于线性索引的切片操作,这样会得到一个向量:(如果A
是行/列向量,那么结果也是行/列向量,如果A
是矩阵,那么结果始终是行向量)
A(m:n)
第 m 到 n 个元素;A(m:end)
第 m 个元素到最后一个元素;
例如 1
2
3
4
5
6
7
8
9
10>> a=[1 2 3; 4 5 6]
a =
1 2 3
4 5 6
>> a(1:3)
ans =
1 4 2
>> a(4:end)
ans =
5 3 6
非常奇怪的设定是,a(:)
会得到一个包含所有元素的列向量,而不是一个行向量。
1 | >> a=[1 2 3; 4 5 6] |
可以利用线性索引展平得到的向量进行一些快捷操作,例如对所有元素求和
1
2
3sum(A(:))
% or
sum(A, [], 'all')
矩阵的位置索引
与线性索引不同,更直观的索引当然是直接指定元素对应的行数和列数(用逗号分隔),对于更高维的数组同理。对于行向量,位置索引和线性索引没有区别。
例如 1
2
3
4
5
6
7>> a=[1 2 3; 4 5 6]
a =
1 2 3
4 5 6
>> a(1,2)
ans =
2
可以一次性访问多个元素,例如 1
2
3
4
5
6
7>> a=[1 2 3; 4 5 6]
a =
1 2 3
4 5 6
>> a(2,[1,3])
ans =
4 6
支持基于位置索引的切片操作,可以提取指定的部分元素得到子矩阵:
A(m:n,p)
第 m 到 n 行,第 p 列;A(m,:)
第 m 行,所有列;A(m:end,:)
第 m 到最后一行,所有列;
例如 1
2
3
4
5
6
7
8
9
10
11>> A = [1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]
A =
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
>> A(1:3,2:4)
ans =
2 3 4
6 7 8
10 11 12
矩阵的索引赋值
MATLAB 基于索引和切片对矩阵进行部分赋值的逻辑非常奇怪,有些看起来不合理的语句竟然都是可以的,有些则又会报错?有一个相关的官方文档Indexed Assignment,但是太简略了,过于灵活的语法导致会很多错误难以发现。
下面的例子均取 1
2
3
4
5
6>> A = [1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]
A =
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
理想的情况下,赋值应该在两个尺寸相符的子矩阵之间进行,例如
1
2
3
4
5
6>> A(1:2,3:4) = [10,20;30,40]
A =
1 2 10 20
5 6 30 40
9 10 11 12
13 14 15 16
之前二元运算中使用的隐式扩展在这里并不允许,例如 1
2
3
4
5
6
7
8>> A(1:2,:) = [1,1,1,1]
Unable to perform assignment because the size of the left side is 2-by-4 and the size of the right side is 1-by-4.
>> A(1:2,:) = [1,1,1,1;1,1,1,1]
A =
1 1 1 1
1 1 1 1
9 10 11 12
13 14 15 16
但是MATLAB其实也并不严格要求两者尺寸一致,例如使用行向量给列赋值是允许的
1
2
3
4
5
6>> A(:,1) = [1,1,1,1]
A =
1 2 3 4
1 6 7 8
1 10 11 12
1 14 15 16
使用列向量给行赋值也是允许的 1
2
3
4
5
6
7
8
9
10
11
12>> A = [1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]
A =
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
>> A(1,:) = [1,1,1,1]'
A =
1 1 1 1
5 6 7 8
9 10 11 12
13 14 15 16
一个常见的情景是用一个常数赋值一部分元素,此时无论赋值对象的尺寸如何,在语法上都是允许的,例如
1
2
3
4
5
6
7
8
9
10
11
12>> A = [1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]
A =
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
>> A(1:3,2:4) = 3
A =
1 3 3 3
5 3 3 3
9 3 3 3
13 14 15 16
在对切片进行赋值时,切片也可以包括当前矩阵不存在的部分,赋值完成后会自动对矩阵进行扩充,例如
1
2
3
4
5
6
7>> S=[1 2]
S =
1 2
>> S(2,:) = 1
S =
1 2
1 1
基于线性索引的部分赋值也是可以的,例如(这里换成行向量也可以,只要保证元素个数相等)
1
2
3
4
5
6>> A(1:8)=[10;20;30;40;50;60;70;80]
A =
10 50 3 4
20 60 7 8
30 70 11 12
40 80 15 16
补充
矩阵的逻辑判断
直接对矩阵使用 if 语句会发生什么? 1
2
3
4
5a = [1, 2, 3];
if a
disp('True')
end
这等价于下面的写法(也更推荐,可读性更高) 1
2
3
4
5
6a = [1, 2, 3];
% 判断所有元素是否都非零
if all(a(:))
disp('True')
end
几种建议写法如下
1 | if all(a(:)) % 所有元素非零 |
all
和 any
支持按指定维度进行运算,在判断时保留指定的维度:
all(A, dim)
: 判断dim
方向上所有元素是否均为非零。any(A, dim)
: 判断dim
方向上是否至少有一个非零元素。
例如 1
2
3
4
5
6
7A = [1 0 3;
4 5 6];
all(A(:)) % false
any(A(:)) % true
all(A, 1) % [true, false, true]
all(A, 2) % [false; true]
矩阵的逻辑运算
MATLAB 支持对矩阵进行逐元素逻辑运算 (&
,
|
,
~
),注意这不是位运算,只关注非零(true)和零(false),返回的是逻辑数组。
例如 1
2
3
4
5
6
7
8
9
10A = [1 0; 2 3];
B = [0 1; 1 0];
C1 = A & B % 逐元素 "AND"
C2 = A | B % 逐元素 "OR"
C3 = ~A % 逐元素 "NOT"
% C1 = [0 0; 1 0]
% C2 = [1 1; 1 1]
% C3 = [0 1; 0 0]
与之类似的短路逻辑运算 (
&&
,||
) 则不支持矩阵,仅能对标量使用。
矩阵的逻辑索引
MATLAB 允许使用 逻辑值 作为索引来筛选数据,以这种方式对矩阵进行读写的效率很高。
例如 1
2
3
4
5
6
7
8A = [1 2 3; 4 5 6];
idx = (A > 2);
% 0 0 1
% 1 1 1
A(idx)
% [4; 5; 3; 6]
也可以直接写作 1
2A(A>2)
% [4; 5; 3; 6]
可以直接对满足条件的部分数据进行修改,只要赋值的数据尺寸满足要求(例如可以隐式扩展)
1
2A(A > 25) = 100; % 把所有大于25的元素设为100
A(A > 10 & A < 50) = 0; % 10 到 50 之间的数置为 0