//文件名:"SMatrix.h"
#pragma once
/*
稀疏矩阵类 SMatrix (Sparse Matrix)
存储结构:一维三元组表
*/
const int MaxSize = 1000; //定义常量MaxSize,表示矩阵中非零数据元素个数的最大值
typedef int ElemType;
struct Triple {
int i; //非零数据元素的行号
int j; //非零数据元素的列号
ElemType e; //非零数据元素的数据值
};
class SMatrix
{
private:
int mu; //矩阵的行数mu
int nu; //矩阵的列数nu
int tu; //矩阵中非零数据元素的个数tu
Triple data[MaxSize]; //矩阵的三元组表
public:
SMatrix(); //构造函数,初始化一个空三元组表
~SMatrix() { delete &mu, &nu, &tu; delete[] &data; }; //析构函数,释放内存。(注:delete 删除的是指针)
SMatrix(int **d, int m, int n); //重载:构造函数,根据原始矩阵创建稀疏矩阵
void MDisplay(); //遍历矩阵,并输出非零数据元素的值
SMatrix *MatrixTrans_Fast(); //矩阵转置算法(快速)
SMatrix *MatrixTrans(); //矩阵转置算法(普通)
SMatrix *MultiMatrix(SMatrix *MB); //矩阵相乘算法
};
//文件名:"SMatrix_Test.cpp"
#include "stdafx.h"
#include <iostream>
#include "SMatrix.h"
using namespace std;
int main()
{
int a1[3][4] = { { 1,0,0,0 },{ 2,0,0,5 },{ 0,4,5,0 } };
int a2[4][3] = { { 0,2,3 },{ 0,3,0 },{ 3,0,5 },{ 1,1,1 } };
SMatrix *sm1 = new SMatrix((int **)a1, 3, 4);
SMatrix *sm2 = new SMatrix((int **)a2, 4, 3);
cout << "矩阵1:" << endl;
sm1->MDisplay();
cout << "矩阵2:" << endl;
sm2->MDisplay();
cout << "矩阵1 转置结果:" << endl;
SMatrix *sm1_t = sm1->MatrixTrans_Fast();
sm1_t->MDisplay();
cout << "矩阵相乘结果:" << endl;
SMatrix *multiResult = sm1->MultiMatrix(sm2);
multiResult->MDisplay();
return 0;
}
//文件名:"SMatrix.cpp"
#include "stdafx.h"
#include <iostream>
#include "SMatrix.h"
using namespace std;
SMatrix::SMatrix()
{
/*
构造函数,初始化
*/
this->mu = 0; //原矩阵行数
this->nu = 0; //原矩阵列数
this->tu = 0; //原矩阵非零元素个数
}
SMatrix::SMatrix(int **d, int m, int n)
{
/*
构造函数(重载):根据原始矩阵创建稀疏矩阵
注:参数 int **d 的入参(二维数组)需要强制转换 (int **)
如:int array[2][3]; (int **) array
***注:此创建的一维稀疏矩阵以行优先顺序存储
*/
//1.参数矩阵为空,调默认构造函数
if (d == NULL || m <= 0 || n <= 0) {
SMatrix();
return;
}
//2.初始化
this->mu = m; //原矩阵行数
this->nu = n; //原矩阵列数
this->tu = 0; //原矩阵非零元素个数
int e = 0; //非零元素临时变量
//3.遍历原矩阵,查找非零元素,并存放入三元组表中
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
e = *((int*)d + n * i + j); //待研究一下!!(注:指针的指针 在数组中无法通过 d[i][j] 寻址,可参看编译原理)
if (e == 0)
continue;
this->data[tu].i = i;
this->data[tu].j = j;
this->data[tu].e = e;
this->tu++;
}
}
}
void SMatrix::MDisplay()
{
/*
打印稀疏矩阵
*/
int t = 0;
for (int i = 0; i < this->mu; i++)
{
for (int j = 0; j < this->nu; j++)
{
//非零元素
if (t < this->tu && this->data[t].i == i && this->data[t].j == j)
{
cout << this->data[t++].e << " ";
}
//零元素
else
cout << 0 << " ";
}
cout << endl;
}
}
SMatrix *SMatrix::MatrixTrans()
{
/*
矩阵转置算法(普通版)
算法实现:按原矩阵列遍历一维稀疏矩阵
注:此类中的一维稀疏矩阵以行优先顺序存储,故转置变为以列优先顺序,需改变次序
复杂度:O(nu*tu) (若非零数据元个数与原矩阵同一数量级,则复杂度:O(nu*nu*mu) )
*/
//0.初始化 转置矩阵 MT 对象
SMatrix *MT = new SMatrix();
MT->mu = this->nu;
MT->nu = this->mu;
MT->tu = this->tu;
//1.遍历矩阵每一列
int p = 0; //计数 MT矩阵 非零元素个数
for (int j = 0; j < this->nu; j++)
{
//2.遍历一维稀疏矩阵,寻找同列的非零元素
for (int q = 0; q < this->tu; q++)
{
if (this->data[q].j == j)
{
//转置赋值
MT->data[p].i = this->data[q].j;
MT->data[p].j = this->data[q].i;
MT->data[p].e = this->data[q].e;
p++;
}
}
}
return MT;
}
SMatrix *SMatrix::MatrixTrans_Fast()
{
/*
矩阵转置算法(快速版)
算法导引:普通算法中,在每次遍历一列中的非零数据元时,同时也遍历到了非当前列的数据元,
由于不知道此非当前列的数据元应置于转置后的什么位置,导致每次跳过,最终导致复杂度升高。
算法实现:
描述:先确定每列首个非零元位置,后每列后续元素根据当前列首个元素位置填放
1.遍历稀疏矩阵,构建一维数组 col_num[],存放每列的非零元个数
2.遍历每列,构建一维数组 col_position[], 存放每列非零元位置
初始化为每列首个非零元位置,后续元素位置逐步加一
初始化公式:col_position[j] = col_num[j-1] + col_position[j-1] , j 为列数
3.遍历稀疏矩阵,根据 col_position[],求得转置稀疏矩阵
注:此类中的一维稀疏矩阵以行优先顺序存储,故转置变为以列优先顺序,需改变次序
复杂度:O(tn+nu)
注:转置稀疏矩阵的另一种表述:
将原矩阵以列优先顺序存储,后以行优先顺序读取,便是转置后的稀疏矩阵
*/
//0.初始化 转置矩阵 MT 对象
SMatrix *MT = new SMatrix();
MT->mu = this->nu;
MT->nu = this->mu;
MT->tu = this->tu;
//1.统计每列非零数据元个数
int *col_num = new int[this->nu]{ 0 }; //每列非零元个数数组,初始化每个元素为 0
for (int i = 0; i < this->tu; i++)
{
col_num[data[i].j]++;
}
//2.初始化 col_position[] 为每列首个非零元素对应转置稀疏矩阵的下标位置
int *col_position = new int[this->nu]{ 0 }; //每列数据元位置数组,初始化每个元素为 0
for (int i = 1; i < this->nu; i++)
{
col_position[i] = col_position[i-1] + col_num[i-1];
}
//3.实现转置
int p = 0; //转置矩阵下标
for (int i = 0; i < this->tu; i++)
{
//获取稀疏矩阵元素对应的转置稀疏矩阵下标,并将当前列元素位置下标加一(为后一个非零元素准备)
p = col_position[this->data[i].j]++;
//转置元素赋值
MT->data[p].i = this->data[i].j;
MT->data[p].j = this->data[i].i;
MT->data[p].e = this->data[i].e;
}
return MT;
}
SMatrix *SMatrix::MultiMatrix(SMatrix *MB)
{
/*
矩阵相乘算法(快速版):利用矩阵快速转置算法理念
算法导引:由于稀疏矩阵是以行优先顺序存储的,所以在矩阵乘法中(左矩阵每一行与右矩阵每一列相乘),
左矩阵 A 的每一行非零元在其稀疏矩阵中是按行优先顺序 连续存储的;
右矩阵 B 的每一列非零元在其稀疏矩阵中是按行优先顺序 不连续存储的。
在一般算发实现上,主要一下几个基本步骤:
1.矩阵 A 每行的非零元( A_ij )都需 与 矩阵 B 对应的 m 行(m == j)的每个非零元 (B_mn) 相乘;
2.矩阵 A 每行的所有非零元 在与 矩阵 B 每列相乘、再相加后,得到一个每行的累加器;
3.这个累加器是具有 一 行 Bn 列 的一维数组。
这样,在实现步骤 1 时,对矩阵 B 的按列遍历,每次都需遍历其整个稀疏矩阵,复杂度很高:O(A.tu * B.tu);
所以采用对 矩阵 B 非零元的按行标记,来简化步骤 1 的实现。
算法实现:
1.统计 B 矩阵的每行非零元个数:按行遍历,结果存放于数组 row_num[]
2.标记 B 矩阵每行首个非零元在其稀疏矩阵中的下标位置,存放于 row_position[] 中
3.实现 A 矩阵每个非零元(因为按行优先存储,故只需按序访问其稀疏矩阵) 与 B 矩阵中对应行的所有非零元(已做标记,节省了全局遍历的时间)相乘
4.将步骤 3 的结果存放于 累加器 row_result[] 中 (按矩阵 A 的行)
5.每遍历完 A 矩阵一行非零元,便将 累加器 row_result[] 中非零结果 放于返回对象的稀疏矩阵中,并初始化 累加器 为0
复杂度:O( B.tu + B.mu + A.tu * B.tu / B.mu + A.mu * B.nu )
其中: B.tu / B.mu 意为:取 B 矩阵 每行平均非零元 个数
*/
//合法化校验
if (this->nu != MB->mu)
{
cout << "两个矩阵不能相乘!" << endl;
return NULL;
}
if (this->tu == 0 || MB->tu == 0)
{
cout << "结果为零阵!" << endl;
return NULL;
}
//0.初始化结果矩阵
SMatrix *MResult = new SMatrix();
MResult->mu = this->mu;
MResult->nu = MB->nu;
MResult->tu = 0;
//1.统计 B 矩阵每行非零元个数
int *row_num = new int[MB->mu]{ 0 }; //每行非零元个数数组,初始化为 0
for (int i = 0; i < MB->tu; i++)
{
row_num[MB->data[i].i]++;
}
//2.标记 B 矩阵每行非零元在其稀疏矩阵中的下标位置,初始化为每行首个非零元位置
int *row_position = new int[MB->mu]{ 0 }; //每行首个非零元在其稀疏矩阵中的下标位置数组
for (int i = 1; i < MB->mu; i++)
{
row_position[i] = row_position[i-1] + row_num[i-1];
}
//3.实现矩阵相乘
//遍历 A 矩阵的一维稀疏矩阵
int EA = 0, EB = 0; //初始化 A、B 矩阵元素 E 为 0
int IA = 0, IB = 0, JA = 0, JB = 0; //初始化 A、B 矩阵元素行列为 0
int NB = 0; //初始化 B 矩阵某一行的非零元素个数
int PB = 0; //初始化 B 矩阵稀疏矩阵下标位置为 0
int *row_result = new int[MB->nu]{ 0 }; //临时存放 A 矩阵每一行 与 B 矩阵每一列 元素乘积 累加结果
for (int p = 0; p < this->tu; p++)
{
//获取 A 矩阵元素 (ia,ja,ea)
IA = this->data[p].i;
JA = this->data[p].j;
EA = this->data[p].e;
IB = JA;
//定位 B 矩阵 IB 行的非零数据元下标位置(对应稀疏矩阵中的)
PB = row_position[IB];
//获取 B 矩阵 IB 行的非零元素个数
NB = row_num[IB];
//遍历 B 矩阵 IB 行上的非零数据元,实现相乘
for (int n = 0; n < NB; n++)
{
IB = MB->data[PB + n].i;
JB = MB->data[PB + n].j;
EB = MB->data[PB + n].e;
row_result[JB] += EA * EB;
}
/*
A 矩阵一行遍历完的判断条件:(2个情况)
1.最后一个非零元时:(p == this->tu - 1)
2.非最后一个非零元时:取下一个非零元的行,判断是否与当前非零元同一行 (this->data[p + 1].i != IA)
则将一行的非零累加结果赋值到结果矩阵(稀疏矩阵)中,并初始化累加器 为0
*/
if (p == this->tu - 1 || this->data[p + 1].i != IA ) //不用担心越界!
{
//读取并初始化 累加器 row_result[]
for (int j = 0; j < MB->nu; j++)
{
//将累加器不为 0 的结果,放入一维稀疏矩阵中
if (row_result[j] != 0)
{
MResult->data[MResult->tu].i = IA;
MResult->data[MResult->tu].j = j;
MResult->data[MResult->tu].e = row_result[j];
MResult->tu++;
}
//初始化 累加器 row_result[] 为 0
row_result[j] = 0;
}
}
}
return MResult;
}