矩阵,是线性表的二维表示。可以用二维数组表示矩阵,而对一些应用广泛且形式特殊的矩阵,如稀疏矩阵和准对角矩阵等,可以进行压缩存储。
如果矩阵的行数和列数相同,则称该矩阵为方阵。有两个特殊类型的方阵:对称矩阵和三角矩阵。由于对称矩阵中有许多重复的元素,因此可以值保留对称矩阵中不重复的部分,从而达到节省空间的目的。这种只保留矩阵的部分元素信息但又能由此还原得到原矩阵的表示方法称为矩阵的压缩存储。
对称矩阵的压缩存储方法是只保留矩阵的下三角元素。三角矩阵的压缩还原与对称矩阵类似。
//矩阵的类型定义
constexpr auto M = 100; //矩阵行数的上限
constexpr auto N = 100; //矩阵列数的上限
typedef int datatype;
struct matrix {
datatype a[M][N];
int row; //矩阵的行数
int col; //矩阵的列数
matrix(int r = 0, int c = 0) :row(r), col(c) {
memset(a, 0, sizeof(a));
}
matrix(datatype b[M][N], int r, int c) :row(r), col(c) { //用数组b初始化当前矩阵
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
a[i][j] = b[i][j];
}
};
//两个矩阵相乘,mat1*mat2,相乘结果作为返回值
matrix mat(matrix mat1, matrix mat2) {
if (mat1.col != mat2.col) {
cout << "矩阵不能相乘!" << endl;
return NULL;
}
matrix mat(mat1.row, mat2.col); //mat的行数和列数分别mat1的行数和mat2的列数
for (int i = 0; i < mat1.row; i++) {
for (int j = 0; j < mat1.col; j++) {
mat.a[i][j] = 0;
for (int k = 0; k < mat1.col; k++) {
mat.a[i][j] += mat1.a[i][k] * mat2.a[k][j];
}
}
}
return mat;
}
//特殊矩阵的表示与操作
//对称矩阵的压缩和还原
//将对称矩阵mat压缩为一维向量,结果作为返回值
vector convert(matrix mat) {
vectorret;
for (int i = 0; i < mat.row; i++) {
for (int j = 0; j <= i; j++) {
ret.push_back(mat.a[i][j]);
}
}
return ret;
}
//将压缩的对称矩阵ve还原成二维表示方法,结果作为返回值
matrix convert(vectorve) {
matrix mat;
int i = 0,j = 0;
for (int k = 0; k < ve.size(); k++) {
j = k - i * (i + 1) / 2; //数学计算,判断列数
mat.a[i][j] = mat.a[j][i] = ve[k];
if (i * (i + 2) / 2 < k) i++; //判断是否进入下一行
}
mat.row = mat.col = i;
return mat;
}
矩阵中非零元素数量与矩阵元素总数的比值称为矩阵的稠密度,即如果一个m*n的矩阵中有t个非零元素,则该矩阵的稠密度y=t/(mn)。如果y小于某个阈值(如0.05),且非零元素的分布没有规律,则称该矩阵为稀疏矩阵;否则称之为稠密矩阵。
为了节省存储空间,在存储稀疏矩阵时可以采用只存储非零元素的压缩存储方式。为了能够将压缩存储的矩阵还原为原矩阵,在存储非零元素时不仅需要保存该元素的值,还需要保存其所在的位置,即每个非零元素的存储信息应为一个三元组(i,j,aij),i和j分别为元素aij的行列位置。
由于稀疏矩阵压缩后元素的数量通常比原矩阵元素的数量要少的多,因此对压缩后的系数矩阵进行操作的效率要比原矩阵操作的效率高。下面以对压缩稀疏矩阵进行转置操作为例进行说明。
矩阵的转置操作其实就是将矩阵所有元素的行号和列号呼唤,因此对于压缩的稀疏矩阵sm,只要将sm.nz中的每一个元素的行号和列号互换即可,但互换后sm.nz中元素的排序并不满足行先列后的要求,最简单的处理方法是将sm.nz进行排序,即通过结构smNode中已经定义的排序规则进行排序,只要调用STL中的算法sort即可。
//矩阵的类型定义
constexpr auto M = 100; //矩阵行数的上限
constexpr auto N = 100; //矩阵列数的上限
typedef int datatype;
struct matrix {
datatype a[M][N];
int row; //矩阵的行数
int col; //矩阵的列数
matrix(int r = 0, int c = 0) :row(r), col(c) {
memset(a, 0, sizeof(a));
}
matrix(datatype b[M][N], int r, int c) :row(r), col(c) { //用数组b初始化当前矩阵
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
a[i][j] = b[i][j];
}
};
//两个矩阵相乘,mat1*mat2,相乘结果作为返回值
matrix mat(matrix mat1, matrix mat2) {
if (mat1.col != mat2.col) {
cout << "矩阵不能相乘!" << endl;
return NULL;
}
matrix mat(mat1.row, mat2.col); //mat的行数和列数分别mat1的行数和mat2的列数
for (int i = 0; i < mat1.row; i++) {
for (int j = 0; j < mat1.col; j++) {
mat.a[i][j] = 0;
for (int k = 0; k < mat1.col; k++) {
mat.a[i][j] += mat1.a[i][k] * mat2.a[k][j];
}
}
}
return mat;
}
//稀疏矩阵压缩存储的类型定义
struct smNode
{
int r, c; //非零元素所在的行号和列号
datatype data; //非零元素的值
bool operator<(smNode sm)const { //排序规则:行先列后,从小到大依次排列。
if (r == sm.r) return c < sm.c;
return r < sm.r;
}
};
struct sMatrix {
smNode nz[N]; //存放所有非零元素
int n, row, col; //n:非零元素的数量;row:原矩阵的行数;col:原矩阵的列数
sMatrix() :n(0), row(0), col(0) {
}
};
//稀疏矩阵的压缩与还原
//将稀疏矩阵mat压缩存储,结果作为返回值
sMatrix sm_convert(matrix mat) {
sMatrix sm;
sm.row = mat.row = mat.col;
for (int i = 0; i < mat.row; i++) {
for (int j = 0; j < mat.col; j++) {
if (mat.a[i][j] != 0) { //非零元素加入结果
int& idx = sm.n; //idx为引用类型
sm.nz[idx++] = { i,j,mat.a[i][j] };
}
}
}
}
//将压缩存储的稀疏矩阵sm还原为原矩阵
matrix sm_convert(sMatrix sm) {
matrix mat(sm.row, sm.col);
for (int i = 0; i < sm.n; i++) {
smNode tmp = sm.nz[i]; //第i个非零元素
mat.a[tmp.r][tmp.c] = tmp.data;
}
return mat;
}
//压缩稀疏矩阵的转置
//求压缩稀疏矩阵的转置,结果也为压缩稀疏矩阵并作为返回值
sMatrix sm_tr(sMatrix sm) {
sMatrix ret;
ret.row = sm.col, ret.col = sm.row, ret.n = sm.n;//转置后的函数和列数与原矩阵交换,非零元素的数量不变
for (int i = 0; i < sm.n; i++) { //考虑每一个非零元素
smNode tmp = sm.nz[i];
ret.nz[i] = { tmp.c,tmp.r,tmp.data }; //颠换行列
}
sort(ret.nz, ret.nz + ret.n); //将元素按指定的顺序排列
return ret;
}