矩阵压缩(降维,对角矩阵,对称矩阵,稀疏矩阵)
1. 二维数组降一维
问题描述:
将二维数组压缩成一维数组,可以节省空间或提高计算效率。常见的方式是按行或按列将二维数组展平为一维数组。
映射公式:
-
按行优先展平(Row-major order):二维数组
A[m][n]
展开成一维数组B[m * n]
,映射公式为:
B[i×n+j]=A[i][j] \mathbf{{\color{Red}B[i×n+j]=A[i][j]}} B[i×n+j]=A[i][j]i
是行索引,j
是列索引,m
是行数,n
是列数。
-
按列优先展平(Column-major order):二维数组
A[m][n]
展开成一维数组B[m * n]
,映射公式为:
B[j×m+i]=A[i][j] \mathbf{{\color{Red}B[j×m+i]=A[i][j]}} B[j×m+i]=A[i][j]i
是行索引,j
是列索引,m
是行数,n
是列数。
示例:按行优先展平
#include <iostream>
using namespace std;
int main() {
int A[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int B[3 * 4]; // 一维数组
// 将二维数组 A 转换为一维数组 B
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
B[i * 4 + j] = A[i][j];
}
}
// 输出一维数组
for (int i = 0; i < 12; ++i) {
cout << B[i] << " ";
}
cout << endl;
return 0;
}
2. 三维数组降二维
问题描述:
将三维数组压缩成二维数组,最常见的方法是将三维数据按特定顺序(如行优先)映射到二维数组的行或列。
映射公式:
-
假设有一个三维数组
A[x][y][z]
我们可以将其压缩为一个二维数组 。
B[x * y][z]
B[i×y+j][k]=A[i][j][k] \mathbf{{\color{Red}B[i×y+j][k]=A[i][j][k]}} B[i×y+j][k]=A[i][j][k]
i
是三维数组的第一个维度索引,j
是第二个维度索引,k
是第三个维度索引,x
、y
、z
分别是三维数组的大小。
示例:三维数组压缩为二维
#include <iostream>
using namespace std;
int main() {
int A[2][3][4] = {
{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}},
{{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}
};
int B[6][4]; // 二维数组
// 将三维数组 A 压缩为二维数组 B
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 4; ++k) {
B[i * 3 + j][k] = A[i][j][k];
}
}
}
// 输出二维数组 B
for (int i = 0; i < 6; ++i) {
for (int j = 0; j < 4; ++j) {
cout << B[i][j] << " ";
}
cout << endl;
}
return 0;
}
3. 三维数组降一维
问题描述:
将三维数组压缩为一维数组,可以按行优先或列优先的方式展开。
映射公式:
- 假设有一个三维数组
A[x][y][z]
,可以将其压缩为一维数组B[x * y * z]
。映射公式如下:
k=i×y×z+j×z+k \mathbf{{\color{Red}k=i×y×z+j×z+k}} k=i×y×z+j×z+k
其中i
,j
,k
分别是三维数组的三个维度索引,x
,y
,z
是三维数组的大小,k
是在一维数组中的索引。
示例:三维数组压缩为一维
#include <iostream>
using namespace std;
int main() {
int A[2][3][4] = {
{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}},
{{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}
};
int B[2 * 3 * 4]; // 一维数组
// 将三维数组 A 压缩为一维数组 B
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 4; ++k) {
int index = i * 3 * 4 + j * 4 + k;
B[index] = A[i][j][k];
}
}
}
// 输出一维数组 B
for (int i = 0; i < 24; ++i) {
cout << B[i] << " ";
}
cout << endl;
return 0;
}
4. 对角矩阵压缩
问题描述:
对角矩阵是一个除了主对角线以外,其他元素都是零的矩阵。我们可以只存储主对角线的元素,从而进行压缩。
映射公式:
- 对于一个大小为
n x n
的对角矩阵A
,只需存储A[i][i]
,i = 1, 2, ..., n
,即压缩为一维数组B
,其大小为n
。
B[i]=A[i][i]fori=1,2,…,n \mathbf{{\color{Red}B[i]=A[i][i]fori=1,2,…,n}} B[i]=A[i][i]fori=1,2,…,n
示例:对角矩阵压缩
#include <iostream>
using namespace std;
int main() {
int A[4][4] = {
{1, 0, 0, 0},
{0, 2, 0, 0},
{0, 0, 3, 0},
{0, 0, 0, 4}
};
int B[4]; // 压缩后的对角矩阵
// 将对角矩阵 A 压缩为一维数组 B
for (int i = 0; i < 4; ++i) {
B[i] = A[i][i];
}
// 输出压缩后的数组 B
for (int i = 0; i < 4; ++i) {
cout << B[i] << " ";
}
cout << endl;
return 0;
}
5、对称矩阵压缩
问题描述:
对称矩阵是指矩阵的元素关于主对角线对称,即对于一个大小为 n×n
的矩阵 A
,如果
A[i][j]=A[j][i]
\mathbf{{\color{Red}A[i][j]=A[j][i]}}
A[i][j]=A[j][i]
,则矩阵 A
是对称矩阵。因此,在存储对称矩阵时,存储对称部分的元素即可,避免存储冗余的元素。
压缩原理:
- 对于对称矩阵
A
,我们可以只存储矩阵的上三角部分或下三角部分(包含主对角线)。因为上三角部分与下三角部分是相等的,存储上三角部分足以重构整个矩阵。
1. 上三角压缩(存储上三角部分,包括主对角线)
在对称矩阵的压缩存储中,常常选择存储上三角矩阵的元素,包括主对角线的元素。对称矩阵的上三角部分的元素个数为:
上三角元素个数=n(n+1)÷2
\mathbf{{\color{Red}上三角元素个数=n(n+1){\div} 2 } }
上三角元素个数=n(n+1)÷2
这个公式表示了存储上三角部分所需的空间,其中 n
是矩阵的维度。
映射公式:
-
对于一个
n×n
的对称矩阵A
,我们可以用一维数组B[]
来存储上三角部分的元素。 -
位置映射公式为:
B[k]=A[i][j],其中k=i(i+1)2+j,且i≤j \mathbf{{\color{Red}B[k]=A[i][j],其中k=i(i+1)2+j,且i≤j}} B[k]=A[i][j],其中k=i(i+1)2+j,且i≤j
其中 i 和 j 为矩阵的行和列索引,k 是压缩后的数组的索引。
示例:
假设我们有一个 4×4 的对称矩阵:
A=(12342567368947910)
A = \begin{pmatrix} 1 & 2 & 3 & 4 \\ 2 & 5 & 6 & 7 \\ 3 & 6 & 8 & 9 \\ 4 & 7 & 9 & 10 \end{pmatrix}
A=12342567368947910
矩阵的上三角部分(包括主对角线)是:
(12345678910)
\begin{pmatrix} 1 & 2 & 3 & 4 \\ & 5 & 6 & 7 \\ & & 8 & 9 \\ & & & 10 \end{pmatrix}
12536847910
我们可以将其压缩成一个一维数组:
B=[1,2,3,4,5,6,7,8,9,10]
B=[1,2,3,4,5,6,7,8,9,10]
B=[1,2,3,4,5,6,7,8,9,10]
2. 压缩存储算法
假设我们有一个大小为n×n
的对称矩阵A
,我们要将其压缩为一维数组 B[]
,存储上三角矩阵的元素。具体步骤如下:
- 对于矩阵 AAA,遍历所有元素
A[i][j]
(其中 i≤j),将其存储到数组B[]
中。 - 利用映射公式,将每个元素
A[i][j]
映射到数组B[]
的位置
映射公式:
-
假设数组
B[]
存储了上三角矩阵的元素,则:
-
k=i(i+1)2+j \mathbf{{\color{Red}k=\frac{i(i+1)}{2}+j}} k=2i(i+1)+j
是元素
A[i][j]
在压缩后的数组B[]
中的索引。 -
其中 i 是行索引,j 是列索引,
i≤j
-
示例代码:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 对称矩阵 A
int A[4][4] = {
{1, 2, 3, 4},
{2, 5, 6, 7},
{3, 6, 8, 9},
{4, 7, 9, 10}
};
// 计算上三角部分的元素个数
int n = 4;
int num_elements = n * (n + 1) / 2;
// 创建压缩后的数组 B
vector<int> B(num_elements);
// 将对称矩阵的上三角部分存储到 B 数组中
int k = 0; // B 数组的索引
for (int i = 0; i < n; ++i) {
for (int j = i; j < n; ++j) {
B[k++] = A[i][j];
}
}
// 输出压缩后的对称矩阵
cout << "Compressed Array (B): ";
for (int i = 0; i < num_elements; ++i) {
cout << B[i] << " ";
}
cout << endl;
return 0;
}
输出:
Compressed Array (B): 1 2 3 4 5 6 7 8 9 10
3. 如何重构原始矩阵
如果我们已经得到了压缩后的数组 B[]
,并且知道它是一个对称矩阵的上三角部分,我们可以根据 B[]
重构原始的对称矩阵 A
。
假设我们已经有了 B[]
,并且知道 n(矩阵的大小),我们可以通过以下步骤来重构原始矩阵 A
:
-
从数组
B[]
中取出每个元素,并将它们放回矩阵的对应位置。 -
对于
B[k]
,它对应的矩阵位置是A[i][j]
,其中
k=i(i+1)2+j \mathbf{{\color{Red}k = \frac{i(i+1)}{2} + j}} k=2i(i+1)+j
重构矩阵代码:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 假设 B 数组已经存储了压缩后的上三角矩阵元素
vector<int> B = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int n = 4; // 矩阵的大小
int k = 0; // B 数组的索引
// 创建一个 n x n 的矩阵 A
int A[4][4] = {0};
// 根据 B 数组重构原始矩阵 A
for (int i = 0; i < n; ++i) {
for (int j = i; j < n; ++j) {
A[i][j] = B[k++];
A[j][i] = A[i][j]; // 对称性
}
}
// 输出重构后的矩阵 A
cout << "Reconstructed Matrix A:" << endl;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cout << A[i][j] << " ";
}
cout << endl;
}
return 0;
}
输出:
Reconstructed Matrix A:
1 2 3 4
2 5 6 7
3 6 8 9
4 7 9 10
6. 稀疏矩阵压缩
问题描述:
稀疏矩阵是指大部分元素为零的矩阵。存储稀疏矩阵时,如果我们直接存储矩阵所有元素,会浪费大量内存。为了节省空间,我们可以使用 压缩存储 的方式,只存储非零元素的值以及它们的位置(索引)。
稀疏矩阵的压缩方法通常有以下几种:
- 压缩行存储(Compressed Row Storage, CRS 或 CSR)
- 压缩列存储(Compressed Column Storage, CCS 或 CSC)
- 三元组表示法(Triplet Representation)
1. 压缩行存储(CSR)
CSR(Compressed Sparse Row) 是一种常见的稀疏矩阵压缩存储格式。它将矩阵按行压缩,并存储每一行的非零元素及其列索引。
映射公式(CSR):
-
假设稀疏矩阵
A[m][n]
,其中只有部分非零元素。CSR将矩阵表示为三个数组:
values[]
:存储所有非零元素。columns[]
:存储每个非零元素对应的列索引。row_ptr[]
:存储每行的非零元素在values[]
数组中的起始位置。
具体地,row_ptr[i]
表示矩阵第 i
行的第一个非零元素在 values[]
中的索引位置,而 row_ptr[i+1]
表示该行的第一个非零元素的下一个位置。
示例:
对于稀疏矩阵:
A=(0030400005000006)
A = \begin{pmatrix} 0 & 0 & 3 & 0 \\ 4 & 0 & 0 & 0 \\ 0 & 5 & 0 & 0 \\ 0 & 0 & 0 & 6 \end{pmatrix}
A=0400005030000006
我们可以使用 CSR 格式压缩它:
values[]
:存储所有非零元素[3, 4, 5, 6]
columns[]
:存储每个非零元素的列索引[2, 0, 1, 3]
row_ptr[]
:表示每行非零元素的起始位置[0, 1, 2, 3, 4]
所以,最终的 CSR 表示为:
values = [3, 4, 5, 6]
columns = [2, 0, 1, 3]
row_ptr = [0, 1, 2, 3, 4]
示例代码(CSR):
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 稀疏矩阵 A
int m = 4, n = 4;
int A[4][4] = {
{0, 0, 3, 0},
{4, 0, 0, 0},
{0, 5, 0, 0},
{0, 0, 0, 6}
};
// CSR存储格式
vector<int> values; // 存储非零值
vector<int> columns; // 存储列索引
vector<int> row_ptr(m + 1, 0); // 存储每行非零值的起始位置
// 扫描稀疏矩阵,将非零值压缩
int count = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (A[i][j] != 0) {
values.push_back(A[i][j]);
columns.push_back(j);
++count;
}
}
row_ptr[i + 1] = count; // 更新当前行的结束位置
}
// 输出压缩后的稀疏矩阵(CSR格式)
cout << "Values: ";
for (int i : values) {
cout << i << " ";
}
cout << endl;
cout << "Columns: ";
for (int i : columns) {
cout << i << " ";
}
cout << endl;
cout << "Row pointers: ";
for (int i : row_ptr) {
cout << i << " ";
}
cout << endl;
return 0;
}
2. 压缩列存储(CCS)
CCS(Compressed Column Storage) 是对稀疏矩阵的一种列优先存储方式,类似于 CSR 存储,但它将矩阵按列进行压缩。它使用以下三个数组来表示稀疏矩阵:
values[]
:存储非零元素。rows[]
:存储每个非零元素对应的行索引。col_ptr[]
:存储每列的非零元素在values[]
数组中的起始位置。
示例:
对于相同的稀疏矩阵:
A=(0030400005000006)
A = \begin{pmatrix} 0 & 0 & 3 & 0 \\ 4 & 0 & 0 & 0 \\ 0 & 5 & 0 & 0 \\ 0 & 0 & 0 & 6 \end{pmatrix}
A=0400005030000006
我们可以使用 CCS 格式压缩它:
values[]
:存储所有非零元素[3, 4, 5, 6]
rows[]
:存储每个非零元素的行索引[0, 1, 2, 3]
col_ptr[]
:表示每列非零元素的起始位置[0, 1, 2, 3, 4]
所以,最终的 CCS 表示为:
values = [3, 4, 5, 6]
rows = [0, 1, 2, 3]
col_ptr = [0, 1, 2, 3, 4]
CCS 示例代码:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 稀疏矩阵 A
int A[4][4] = {
{0, 0, 3, 0},
{4, 0, 0, 0},
{0, 5, 0, 0},
{0, 0, 0, 6}
};
// 矩阵的行数和列数
int m = 4, n = 4;
// 创建 CCS 存储格式的数组
vector<int> values; // 存储非零值
vector<int> rows; // 存储行索引
vector<int> col_ptr(n + 1, 0); // 存储每列非零值的起始位置
// 扫描稀疏矩阵,将非零值压缩到 CCS 格式
int count = 0;
for (int j = 0; j < n; ++j) { // 遍历每一列
for (int i = 0; i < m; ++i) { // 遍历每一行
if (A[i][j] != 0) {
values.push_back(A[i][j]); // 存储非零值
rows.push_back(i); // 存储行索引
count++;
}
}
col_ptr[j + 1] = count; // 更新当前列的结束位置
}
// 输出 CCS 格式的稀疏矩阵
cout << "Values: ";
for (int i : values) {
cout << i << " ";
}
cout << endl;
cout << "Rows: ";
for (int i : rows) {
cout << i << " ";
}
cout << endl;
cout << "Column Pointers: ";
for (int i : col_ptr) {
cout << i << " ";
}
cout << endl;
// 重构矩阵 A(根据 CCS 格式)
int A_reconstructed[4][4] = {0};
for (int j = 0; j < n; ++j) {
for (int k = col_ptr[j]; k < col_ptr[j + 1]; ++k) {
int i = rows[k];
A_reconstructed[i][j] = values[k];
}
}
// 输出重构后的矩阵 A
cout << "Reconstructed Matrix A:" << endl;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
cout << A_reconstructed[i][j] << " ";
}
cout << endl;
}
return 0;
}
3. 三元组表示法
三元组表示法(Triplet Representation)是另一种压缩稀疏矩阵的方式,通常用于存储稀疏矩阵的所有非零元素和它们的位置。每个非零元素都被表示为一个三元组 (i, j, value)
,其中 i
是行索引,j
是列索引,value
是非零元素的值。
映射公式:
- 将每个非零元素存储为一个三元组
(i, j, value)
,然后将这些三元组存储在一个数组中。
示例:
对于稀疏矩阵:
A=(0030400005000006)
A = \begin{pmatrix} 0 & 0 & 3 & 0 \\ 4 & 0 & 0 & 0 \\ 0 & 5 & 0 & 0 \\ 0 & 0 & 0 & 6 \end{pmatrix}
A=0400005030000006
其三元组表示为:
(0, 2, 3)
(1, 0, 4)
(2, 1, 5)
(3, 3, 6)
示例代码(三元组表示法):
#include <iostream>
#include <vector>
using namespace std;
struct Triplet {
int row, col, value;
};
int main() {
int A[4][4] = {
{0, 0, 3, 0},
{4, 0, 0, 0},
{0, 5, 0, 0},
{0, 0, 0, 6}
};
vector<Triplet> triplets;
// 扫描矩阵,存储非零元素
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
if (A[i][j] != 0) {
triplets.push_back({i, j, A[i][j]});
}
}
}
// 输出三元组表示
for (const auto& triplet : triplets) {
cout << "(" << triplet.row << ", " << triplet.col << ", " << triplet.value << ")\n";
}
return 0;
}