概述

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>> [1 2 3 4]
ans =
1 2 3 4
>> [1 2 3;4 5 6]
ans =
1 2 3
4 5 6
>> [1 2
3 4]
ans =
1 2
3 4
>> [5 6;
7 8]
ans =
5 6
7 8

(如果尺寸不合法则会报错,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
2
3
4
5
6
7
>> 1:2:10
ans =
1 3 5 7 9

% for(i=a;i<=c;i++){
% ...
% }
  • a:b 相当于把中间的默认步长b=1省略。

与之类似的是linspace函数:

  • linspace(a,b,n) 基于闭区间生成行向量,第一个值是 a,最后一个值是 b,中间为等差数列,一共 n 个数;
  • 缺省 n 时,默认取 n=100。

拼接矩阵

我们可以很方便地把两个矩阵拼起来,包括水平拼接和竖直拼接,它们分别要求两个矩阵有相同的行数或列数。

1
2
3
4
5
6
7
8
9
10
>> a=[1 2];b=[3 4]
b =
3 4
>> [a b]
ans =
1 2 3 4
>> [a;b]
ans =
1 2
3 4

矩阵的基础运算

矩阵可以直接和数相加减,默认会逐个元素进行。

1
2
3
4
>> zeros(2,3)+2
ans =
2 2 2
2 2 2

可以对矩阵执行内置函数,默认会逐个元素执行。

1
2
3
4
>> a=[1 2 3;4 5 6];sin(a)
ans =
0.8415 0.9093 0.1411
-0.7568 -0.9589 -0.2794

矩阵的两种转置:共轭转置A',转置A.'。(对于实数矩阵两者一样)

矩阵乘法:A*B执行标准的矩阵乘法。

1
2
3
4
>> A=[1 1;2 2];B=[1 2;3 4];A*B
ans =
4 6
8 12

两个矩阵进行逐个元素的乘法:A.*B

1
2
3
4
>> A=[1 1;2 2];B=[1 2;3 4];A.*B
ans =
1 2
6 8

同理,还有逐个元素的乘方.^。 尤其注意,这里的运算例如+.*对于尺寸兼容的 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 的那个矩阵可以通过在这个维度上的简单复制达到相同的尺寸,对于 m2n2m3n3 也同理。

对尺寸不同,但是兼容的两个矩阵所进行的二元运算,可能会自动触发隐式扩展,例如

1
2
A=[1 2 3];          % 行向量
B=[10;20;30;40]; % 列向量

它们做逐个元素的乘法时,会自动对尺寸进行扩展,在这个例子中就是都变成尺寸 (3,4) 的矩阵,在扩展的维度上只是单纯的复制

1
2
3
4
5
6
7
8
9
10
A->
1 2 3
1 2 3
1 2 3

B->
10 10 10
20 20 20
30 30 30
40 40 40

然后进行运算,计算A+BA.*B得到的结果分别为

1
2
3
4
5
6
7
8
9
10
11
A+B =
11 12 13
21 22 23
31 32 33
41 42 43

A.*B =
10 20 30
20 40 60
30 60 90
40 80 120

注意:

  • 行向量和列向量的乘法是向量乘法,但是逐元素乘法会得到矩阵。
  • 列向量和行向量的乘法因为隐式扩展,完全等效于逐元素乘法,两者都会得到矩阵。

扩充矩阵

MATLAB的矩阵尺寸不是固定的,甚至允许在赋值时直接扩充矩阵尺寸(当然这会产生很多内存拷贝),例如扩充向量

1
2
3
4
5
a = [];
a(1) = 1;
a(2) = 2;

a % [1 2]

扩充矩阵

1
2
3
4
5
6
7
8
b = 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
2
A = 1:10;
B = reshape(A,[5,2])

可以很方便地搭配size函数使用,例如

1
2
C = [1 2 3 4 5 6 7 8 9 10];
D = reshape(C,size(B))

第二种则是分别提供每一个维度的长度,此时可以使用一个[]缺省,MATLAB会自动计算缺少的维度长度,例如

1
2
A = 1:10;
B = reshape(A,[],2) % size = [5,2]

注意:

  • reshape 和 resize 这两个名称在不同语言中经常混淆;
  • 较新版本的 MATLAB 也提供了 resize 函数,但是作用只是用来在尾部加0或移除元素,也就是只考虑实际数据长度进行截断或延长,通常不需要。

矩阵的线性索引

无论是矩阵还是退化的行向量列向量,或者高维矩阵,每一个元素都可以使用一个正整数进行索引,称为线性索引。

与之不同的是,在 C/C++ 和 Numpy 中,二维数组通常是通过一维数组的数组实现的,因此对二维数组用一个整数索引会得到一个一维数组。

线性索引从 1 开始,完全对应于矩阵在内存中列主序的排列顺序。(在同一列中,相邻的元素就在内存中相邻)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>> a=[1 2 3; 4 5 6]
a =
1 2 3
4 5 6
>> a(1)
ans =
1
>> a(2)
ans =
4
>> a(3)
ans =
2
>> a(4)
ans =
5

支持基于线性索引的切片操作,这样会得到一个向量:(如果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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>> a=[1 2 3; 4 5 6]
a =
1 2 3
4 5 6
>> a(1:end)
ans =
1 4 2 5 3 6
>> a(:)
ans =
1
4
2
5
3
6

可以利用线性索引展平得到的向量进行一些快捷操作,例如对所有元素求和

1
2
3
sum(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
5
a = [1, 2, 3];

if a
disp('True')
end

这等价于下面的写法(也更推荐,可读性更高)

1
2
3
4
5
6
a = [1, 2, 3];

% 判断所有元素是否都非零
if all(a(:))
disp('True')
end

几种建议写法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if all(a(:)) % 所有元素非零
disp('All elements are nonzero');
end

if any(a(:)) % 至少有一个非零元素
disp('At least one element is nonzero');
end

if isempty(a) % 矩阵是否为空
disp('Matrix is empty');
end

if isnan(a) || isinf(a) % 矩阵是否含有NaN或Inf
disp('At least one element is NaN or Inf');
end

allany 支持按指定维度进行运算,在判断时保留指定的维度:

  • all(A, dim): 判断 dim 方向上所有元素是否均为非零。
  • any(A, dim): 判断 dim 方向上是否至少有一个非零元素。

例如

1
2
3
4
5
6
7
A = [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
10
A = [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
8
A = [1 2 3; 4 5 6];

idx = (A > 2);
% 0 0 1
% 1 1 1

A(idx)
% [4; 5; 3; 6]

也可以直接写作

1
2
A(A>2)
% [4; 5; 3; 6]

可以直接对满足条件的部分数据进行修改,只要赋值的数据尺寸满足要求(例如可以隐式扩展)

1
2
A(A > 25) = 100;  % 把所有大于25的元素设为100
A(A > 10 & A < 50) = 0; % 10 到 50 之间的数置为 0