数据结构之稀疏矩阵(C++)

//文件名:"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;
}



猜你喜欢

转载自blog.csdn.net/weixin_39469127/article/details/80303878
今日推荐