如何用Armadillo更高效提取矩阵下三角元素至向量?
嘿,这个问题我之前也琢磨过!首先得给你明确一点:因为Armadillo里矩阵的下三角元素在内存中是分散存储的,所以想直接用指针指向原矩阵内存来创建向量是行不通的——向量要求内存连续,而下三角元素散落在矩阵的不同行/列位置,这一点确实没法绕开。不过我们可以用更高效的方式来完成这个拷贝,比你现在的实现和那个find方案靠谱多了:
先优化你的手动循环
你当前的嵌套循环里多了个if(i<j)的判断,这会增加分支预测的开销,而且内层循环从j=1开始也没必要。我们可以直接调整循环范围,只遍历下三角区域,完全去掉条件判断:
vec trimat2vec(const mat& inmat){ const int K = inmat.n_rows; const int p = K*(K-1)/2; vec out(p); int counter = 0; // 直接遍历严格下三角区域(i > j) for(int i = 1; i < K; ++i){ for(int j = 0; j < i; ++j){ out(counter++) = inmat(i,j); } } return out; }
这样改完,循环里没有多余的判断,速度会比原来的版本快不少。
用Armadillo内置工具偷懒(还更高效)
其实Armadillo早就内置了处理三角矩阵的视图函数,比如trimatl()——它能直接获取矩阵的下三角部分(包含对角线),如果要严格下三角(不含对角线),就加个k=-1参数。然后配合vectorise()函数,就能直接把这个视图转成向量:
vec trimat2vec(const mat& inmat){ // 严格下三角版本,要包含对角线就去掉,k=-1 return vectorise(trimatl(inmat, k=-1)); }
这里要说明下:trimatl()返回的是一个视图对象,不会创建新的矩阵拷贝,只有当vectorise()把它转成向量时才会做一次内存拷贝。但这个拷贝是Armadillo内部优化过的,用了底层的内存操作,效率通常比手动循环的赋值要高。
为啥那个find方案不行?
你提到的find(trimatu(inmat)!=0)真的不推荐:首先,双精度矩阵里做精确零值判断本身就不安全——浮点运算的精度误差可能会把本该是零的数变成极小的非零值,反过来也可能把非零值误判成零;其次,find操作需要遍历整个矩阵并做条件判断,效率远不如直接遍历目标三角区域或者用内置的视图工具。
再唠唠指针的事儿
还是得再强调下:Armadillo矩阵默认是列优先存储的,拿个3x3矩阵举例,它的内存布局是这样的:
(0,0), (1,0), (2,0), (0,1), (1,1), (2,1), (0,2), (1,2), (2,2)
严格下三角元素是(1,0), (2,0), (2,1),这些元素在内存里的位置是不连续的,而向量必须是连续的内存块,所以根本没法直接用指针把原矩阵的下三角元素映射成向量——必须做一次拷贝,把分散的元素整理到连续内存里。
内容的提问来源于stack exchange,提问作者Inferrator

