【C++算法】稀疏矩阵的乘法实现(行逻辑链接法)

算法背景:
①稀疏矩阵的类表示法:

template <class T>
class sparseMatrix
{
private:
	int rows;
	int cols;   //这里的rows和cols存的是矩阵理论有的行列数
	matrixTerm<T>* terms;  //这里的terms存的是实际的矩阵结构
	//每一个空间存的都是行数、列数、相应的元素值
	int size;
public:
	sparseMatrix() : rows(0), cols(0), size(0) {}
	sparseMatrix(int row, int col) : rows(row), cols(col), size(0) {}
	void multiply(const sparseMatrix<T>& m);
	sparseMatrix<T> operator=(const sparseMatrix<T>& m);
};

②三元组表的结构体表示法:

template <class T>
struct matrixTerm
{
	int row;
	int col;
	T value;
	operator T() const { return value; }
};

主要算法:行逻辑链接法。前提条件是三元组表按照行主索引法进行映射。同稀疏矩阵的转置操作一样,乘法操作也需要定义两个辅助的数组row_nums[i]和row_first_position[i],分别表示矩阵A第i行的非零元素数目和第i行第1个非零元素所在三元组表中对应的索引。

int* row_nums = new int[rows + 1];   //各行的元素数目
int* row_first_position = new int[rows + 1];  //各行第一个非零元对应的索引

对数组row_nums的赋值如下:

for (int i = 0; i <= rows; i++)
		row_nums[i] = 0;   //初始化row_nums
for (int i = 0; i < size; i++)
		row_nums[terms[i].row]++;     //赋值row_nums

terms[i]表示矩阵A三元组表第i号索引的元素,terms[i].row表示矩阵A这行存在元素。
对数组row_first_position的赋值如下:

for (int i = 0; i <= rows; i++)   //如果第i行没有元素,则置为-1
	row_first_position[i] = -1;
row_first_position[1] = 0;       //第一行第一个非零元一定在0号索引
for (int i = 2; i <= rows; i++)   //遍历三元组表
	row_first_position[i] = row_first_position[i - 1] + row_nums[i - 1];

for循环中从第二列开始逻辑上“遍历”矩阵A,分析一下i = 2时,position[2]为第2列第一个非零元应在的索引位置,它显然等于第一行第一个非零元所应在的索引位置 + 第一行的非零元个数,这也是逻辑上显然成立的。对于之后的每一个i,也显然都有此行第一个非零元所应在的索引位置 = 上一行第一个非零元所应在的索引位置 + 上一行的非零元素个数。
因此,公式row_first_position[i] = row_first_position[i - 1] + row_nums[i - 1]是显然的。
然后,初始化累加器:

T* accumulator = new T[m.cols + 1];      //B有m.cols列,所以定义m.cols大小的累加器
for (int i = 0; i <= m.cols; i++)
	accumulator[i] = 0;          //初始化累加器,分别表示各个列

就可以进入for循环,进入矩阵的乘法了。
在此之前,先看一下累加器的使用规则:
假设有m行k列矩阵A和k行n列的矩阵B,将二者相乘放到矩阵C中,矩阵C是m行n列的。假设有A、B矩阵如下所示:
在这里插入图片描述
显然结果的C矩阵是5行5列的。根据矩阵的乘法公式,先列出计算C矩阵第一行的式子:

由于是WPS写的文章,不好复制公式,所以直接截图过来了
接着进行下一次计算时,把后面的一项分别存到累加器中:

在这里插入图片描述
对于矩阵A的某行的第一个非零元素,当遍历到它时,需要将它执行B.cols次乘法,即累加器的大小是B.cols + 1,因为不存在第0列。遍历到A11时,需要全部扫描一遍B的三元组表,把那些“矩阵A的列索引 = 矩阵B的行索引”的元素与A11相乘,存储到第B.terms[j].col号迭代器中去。然后,A11使用完毕,此时把row_first_position[i]++,表示第i行第一个元素所在三元组表的索引+1(因为三元组表是按照行主映射的,同一行相邻两个非零元的索引一定相邻)。也即,此时遍历到A12了。对于A12,也执行相同的算法,把那些“矩阵A的列索引 = 矩阵B的行索引”的元素与A12相乘,累加到第B.terms[j].col号迭代器中去。
显然,执行乘法需要两个for循环,外层循环“逻辑上”遍历A矩阵的某一行的非零元素,内层循环遍历B矩阵的三元组表,筛选那些符合条件的元素并累加到累加器中。

for (int k = 1; k <= row_nums[i]; k++) //逻辑上遍历A的第i行
{
	matrixTerm<T>& row_num = terms[row_first_position[i]];
	for (int j = 0; j < m.size; j++)  //对于第i行的各个元素,都遍历一遍B矩阵
	{
		matrixTerm<T>& col_num = m.terms[j];
		if (row_num.row == i && row_num.col == col_num.row)  //确保还在当前行
			accumulator[col_num.col] += row_num.value * col_num.value;
	}
	row_first_position[i]++;
}

为了方便,用row_num表示当前第i行使用的元素,col_num表示当前第j列使用的元素。外层循环的判断用到了row_nums数组,第i行有x个非零元素,就执行x次外层循环。if语句判断row_num还在第i行,且行元素和列元素满足累加条件。内层循环完之后,把row_first_position[i]++,表示三元组表的索引加1

for (int k = 1; k <= m.cols; k++)
{
	if (accumulator[k] != 0)   //矩阵C的第i行第k列有值
	{
		result.size++;
		result.terms[cursor_C].row = i;
		result.terms[cursor_C].col = k;
		result.terms[cursor_C].value = accumulator[k];
		cursor_C++;
	}
}

紧接着,把第i行1~m.cols号累加器中的非零值按照行主映射存到结果矩阵中去。存完后,清空累加器,继续进入下一次循环。
这个算法需要嵌套3层for循环:

在这里插入图片描述

注意它们之间的联系!
完整函数代码附下:


template <class T>
void sparseMatrix<T>::multiply(const sparseMatrix<T>& m)
{
	sparseMatrix<T> result;  //结果矩阵
	//使结果矩阵的行数等于A矩阵的行数,列数等于B矩阵的列数
	result.rows = rows;
	result.cols = m.cols;
	int cursor_C = 0;    //使用cursor_C来标记结果矩阵的三元组表
	result.terms = new matrixTerm<T>[rows * m.cols];  //按照最大情况来初始化
	result.size = 0;

	//用一个数组来表示A每一行第一个元素在三元组表中对应的索引
	int* row_nums = new int[rows + 1];   //各行的元素数目
	int* row_first_position = new int[rows + 1];  //各行第一个非零元对应的索引

	for (int i = 0; i <= rows; i++)
		row_nums[i] = 0;   //初始化row_nums
	for (int i = 0; i < size; i++)
		row_nums[terms[i].row]++;     //赋值row_nums

	for (int i = 0; i <= rows; i++)   //如果第i行没有元素,则置为-1
		row_first_position[i] = -1;
	row_first_position[1] = 0;       //第一行第一个非零元一定在0号索引
	for (int i = 2; i <= rows; i++)   //遍历三元组表
		row_first_position[i] = row_first_position[i - 1] + row_nums[i - 1];
	
	T* accumulator = new T[m.cols + 1];      //B有m.cols列,所以定义m.cols大小的累加器
	for (int i = 0; i <= m.cols; i++)
		accumulator[i] = 0;          //初始化累加器,分别表示各个列

	for (int i = 1; i <= rows; i++)    //逻辑上遍历矩阵A的每一行
	{
		if (row_first_position[i] == -1)   //矩阵的第i行为空
		{
			//do nothing
		}
		else   //矩阵的第i行非空,此时要遍历B的三元表,并在累加器中存值
		{
			for (int k = 1; k <= row_nums[i]; k++) //逻辑上遍历A的第i行
			{
				matrixTerm<T>& row_num = terms[row_first_position[i]];
				for (int j = 0; j < m.size; j++)  //对于第i行的各个元素,都遍历一遍B矩阵
				{
					matrixTerm<T>& col_num = m.terms[j];
					if (row_num.row == i && row_num.col == col_num.row)  //确保还在当前行
						accumulator[col_num.col] += row_num.value * col_num.value;
				}
				row_first_position[i]++;
			}
			//如此便得到了C矩阵第i行的各个元素的值,分别存在累加器中
			for (int k = 1; k <= m.cols; k++)
			{
				if (accumulator[k] != 0)   //矩阵C的第i行第k列有值
				{
					result.size++;
					result.terms[cursor_C].row = i;
					result.terms[cursor_C].col = k;
					result.terms[cursor_C].value = accumulator[k];
					cursor_C++;
				}
			}
			//如此便运算完了第i行并成功存值,接下来要归零累加器
			for (int k = 1; k <= m.cols; k++)
				accumulator[k] = 0;          //初始化累加器,分别表示各个列
		}
	}
	*this = result;
}
  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值