如何在不知道如何矢量化的情况下进行矢量化:
首先,我将只讨论矢量化第一个双循环,您可以对第二个循环遵循相同的逻辑。
我试图从头开始展示一个思考过程,所以虽然最终答案只有 2 行长,但值得看看初学者如何尝试获得它。
首先,我建议在简单的情况下“按摩”代码,以感受一下。例如,我使用了n=3 和v=1:6 并运行了第一个循环,这就是B 的样子:
[N M]=size(B)
N =
9
M =
27
imagesc(B);
所以你可以看到我们得到了一个像矩阵一样的楼梯,这是非常规则的!我们只需要将正确的矩阵索引分配给v 的正确值即可。
有很多方法可以实现这一点,有些方法比其他方法更优雅。最简单的方法之一是使用函数find:
pos=[find(B==v(1)),find(B==v(2)),find(B==v(3)),...
find(B==v(4)),find(B==v(5)),find(B==v(6))]
pos =
1 10 19 28 37 46
29 38 47 56 65 74
57 66 75 84 93 102
85 94 103 112 121 130
113 122 131 140 149 158
141 150 159 168 177 186
169 178 187 196 205 214
197 206 215 224 233 242
上面的值是矩阵B 的linear indices,其中找到了v 的值。每列代表B 中特定值v 的linear index。例如,索引[1 29 57 ...] 都包含值v(1),等等......每一行都包含v,因此,索引[29 38 47 56 65 74] 包含v=[v(1) v(2) ... v(6)]。可以注意到,每一行的索引差是9,或者说,每个索引之间有一个步长N,有6个,正好是向量v的长度(也是@得到的) 987654342@)。对于列,相邻元素之间的差为 28,或者步长为M+1。
我们只需要根据这个逻辑在适当的索引中分配v 的值。一种方法是编写每个“行”:
B([1:N:numel(v)*N]+(M+1)*0)=v;
B([1:N:numel(v)*N]+(M+1)*1)=v;
...
B([1:N:numel(v)*N]+(M+1)*(N-2))=v;
但这对于大N-2 来说是不切实际的,所以如果你真的想要的话,你可以在 for 循环中做到这一点:
for kk=0:N-2;
B([1:N:numel(v)*N]+(M+1)*kk)=v;
end
Matlab 提供了一种更有效的方法来使用 bsxfun 一次获取所有索引(这取代了 for 循环),例如:
ind=bsxfun(@plus,1:N:N*numel(v),[0:(M+1):M*(N-1)+1]')
所以现在我们可以使用ind 将v 分配给矩阵N-1 次。为此,我们需要将ind“扁平化”为行向量:
ind=reshape(ind.',1,[]);
并将v 与自身连接N-1 次(或再制作N-1 个v 的副本):
vec=repmat(v,[1 N-1]);
我们终于得到了答案:
B(ind)=vec;
长话短说,写得紧凑,我们得到了一个 2 行解决方案(已知大小 B:[N M]=size(B)):
ind=bsxfun(@plus,1:N:N*numel(v),[0:(M+1):M*(N-1)+1]');
B(reshape(ind.',1,[]))=repmat(v,[1 N-1]);
对于n=9,矢量化代码在我的机器上快了约 850 倍。 (小n 会不那么重要)
由于得到的矩阵大部分是由零组成的,你不需要将这些分配给一个完整的矩阵,而是使用一个稀疏矩阵,这里是完整的代码(非常相似):
N=3^(n-1);
M=3^n;
S=sparse([],[],[],N,M);
ind=bsxfun(@plus,1:N:N*numel(v),[0:(M+1):M*(N-1)+1]');
S(reshape(ind.',1,[]))=repmat(v,[1 N-1]);
对于n=10,我只能运行稀疏矩阵代码(否则内存不足),而在我的机器上大约需要 6 秒。
现在尝试将其应用于第二个循环...