数据结构与算法-稀疏矩阵(三元表的实现)

        稀疏矩阵,矩阵中非零元素的个数远小于矩阵元素总数,且非零元素的分布没有规律,可以称该矩阵为稀疏矩阵。如果还用一个row*col的矩阵去表示这些信息,浪费空间,可以对矩阵换一种存储结构,只需要记录矩阵中非零元素的位置和值。也就是通过三元表来完成一个稀疏矩阵的表示,此外还有通过链表,这里讲一下三元表的稀疏矩阵以及稀疏矩阵的打印,转置。               三元表的结构就是下图,注意它是按照先行后列升序的顺序存放的,之所以按照这个顺序存放,涉及打印的时候只需要一遍遍历稀疏矩阵判断就可以达到打印的目的,如果三元表中的每一项是乱序的还要达到打印的效果,每打印稀疏矩阵一个元素就要判断这个元素是否存在三元表,大大增加了时间复杂度。

 
typedef struct  TRIPLE{
	int row;
	int col;
	int value;
}TRIPLE;

typedef struct TSMATRIX {
	TRIPLE *triple;	
	int matrix_row;
	int matrix_col;
	int num_count;    //这个相当于triple数组的长度
}TSMATRIX;

 

1、三元表的初始化

 

        初始化工作内容有:确定稀疏矩阵中元素个数以及确定稀疏矩阵的行列大小,然后给三元表申请好空间等待数据的录入。

void initSparseMatrix(TSMATRIX **tsMatrix, int matrix_row, int matrix_col, int num_count) {
	TSMATRIX *tp;

	if(NULL != (*tsMatrix) || (matrix_row*matrix_col < num_count)
		|| matrix_col <= 0 || matrix_row <= 0) {

		printf("ERROR INPUT DATA\n");
		return;
	}

	tp = (TSMATRIX *)malloc(sizeof(TSMATRIX));

	if(tp != NULL) {  //有没有分配到空间怎么说
		tp->triple = (TRIPLE *)calloc(sizeof(TRIPLE), num_count);
		tp->matrix_row = matrix_row;
		tp->matrix_col = matrix_col;
		tp->num_count = num_count;
		*tsMatrix = tp;
	}
}

2、数据的录入

    按照先行后列升序的顺序录入数据到三元表中, (row, col, value)为一组数据;

void insertDataToMatrix(TSMATRIX *tsMatrix)  {
	int i;
	int row;
	int col;
	int value;

	printf("Insert data by row and then col ascending(row, col, value)\n");
	for(i = 0; i < tsMatrix->num_count; i++) {
		printf("[%d/%d] data: ", i+1, tsMatrix->num_count);
		scanf("%d%d%d", &row, &col, &value);
		tsMatrix->triple[i].row = row;
		tsMatrix->triple[i].col = col;
		tsMatrix->triple[i].value = value;
	}
}

3、打印稀疏矩阵

        稀疏矩阵的信息是从三元表中得到的,三元表从第一个开始判断,然后逐个判断稀疏矩阵的位置在三元表中是否存在,存在的话就打印三元表的元素,同时三元表到下一个三元,不存在的话就打印0元素。

void showSparseMatrix(const TSMATRIX *tsMatrix) {
	int i;
	int j;
	int index = 0;

	for(i = 0; i < tsMatrix->matrix_row; i++) {
		for(j = 0; j < tsMatrix->matrix_col; j++) {
			if(tsMatrix->triple[index].row == i &&
			 tsMatrix->triple[index].col == j) {
				printf("%3d", tsMatrix->triple[index++].value);
			}else {
				printf("%3d", 0);
			}
		}
		puts("");
	}
	puts("");
}

4、转置

    稀疏矩阵的转置实质上是改变三元表的存储信息,行数据和列数据的交换,但不能是单纯的交换,交换之后的三元变也应该保持先行后列升序的规则,这一点是稀疏矩阵转置的难点,如何保持转置后的三元表仍保持先行后列升序的顺序。

    方法1:每次从原三元表中按照列下标递增的方式逐个放到转置三元表里面去,转置后的三元表列是原三元表的行,转置后的三元表行是原三元表的列,那就从列着手,从小到大把列放进去,行坐标也是升序的,所以放到转置后的三元表行列也是升序的。这种方法可以达到转置的目的,但是时间复杂度高,两层循环是三元表的长度乘以矩阵的列,而不是一次遍历三元表。

//按照列下标递增的顺序去找tsMatrix->triple[j].col == i
//在三元表里面当前元素的列等于i就找到一个
TSMATRIX *transposeSparseMatrix_1(const TSMATRIX *tsMatrix) {
	int i;
	int j;
	int index = 0;
	TSMATRIX *transposeMatrix = NULL;

	initSparseMatrix(&transposeMatrix, tsMatrix->matrix_col, tsMatrix->matrix_row, tsMatrix->num_count);
	for(i = 0; i < tsMatrix->matrix_col; i++) {
		for(j = 0; j < tsMatrix->num_count; j++) {
			if(tsMatrix->triple[j].col == i) {
				transposeMatrix->triple[index].row = tsMatrix->triple[j].col;
				transposeMatrix->triple[index].col = tsMatrix->triple[j].row;
				transposeMatrix->triple[index++].value = tsMatrix->triple[j].value;
			}	
		}
		if(index >= tsMatrix->num_count) {   //如果发现此时三元表已经填满就可以不用再进行外层循环了
			break;    
		}
	}

	return transposeMatrix;
}

    一次定位快速转置:首先统计原矩阵中每列有多少个非零元素,也就是转置完B的每行有多少个元素;然后计算原矩阵中每一列的元素在三元表中第几个出现,同样就是转置矩阵B每一行的元素在三元表中第几个出现;通过计算的出现位置的矩阵,挨个遍历原三元表,直接放到转置矩阵的准确位置,一遍遍历三元表就可完成。

//一次快速定位转置 总体分三步
//1、先对原三元表中的元素统计 每列有多少个非0元素,也就是转置完B的每行有多少个非0元素
//2、通过第一步计算B矩阵每一行元素在三元表中第几个开始
//3、把三元表A的元素直接放到B的三元表里面去 一次遍历三元表A防止B中去
TSMATRIX *transposeSparseMatrix_3(const TSMATRIX *tsMatrix) {
	int *nums = NULL;
	int *postion = NULL;
	int i;
	TSMATRIX *transposeMatrix = NULL;

//对转转置后的三元表初始化申请空间	
	initSparseMatrix(&transposeMatrix, tsMatrix->matrix_col, tsMatrix->matrix_row, tsMatrix->num_count);
//申请一个记录元素出现位置的矩阵 长度和原稀疏矩阵列保持一致	
	postion = (int *)calloc(sizeof(int), tsMatrix->matrix_col);
	nums = (int *)calloc(sizeof(int), tsMatrix->matrix_col);
	for(i = 0; i < tsMatrix->num_count; i++) {
//遍历原三元表,统计矩阵的每一列有多少个非零元素 
//对于矩阵
//0 14 0 0  -5
//0 -7 0 0   0
//36 0 0 28  0
//每一列包含的元素个数为 1 2 0 1 1		
		nums[tsMatrix->triple[i].col]++;
	}
	for(i = 1, postion[0]= 1; i < tsMatrix->matrix_col; i++) {
//通过上一步计算的矩阵中每一列有多少个非零元素 计算矩阵的每一列元素在三元表出现的位置
//即是1 2 4 4 5	
		postion[i] = nums[i - 1] + postion[i - 1];
	}
	

	for(i = 0; i < tsMatrix->num_count; i++) {
//原三元表当前这一项在转置的三元表中应该出现的位置		
		int index = postion[tsMatrix->triple[i].col];
		transposeMatrix->triple[index - 1].row = tsMatrix->triple[i].col;
		transposeMatrix->triple[index - 1].col = tsMatrix->triple[i].row;
		transposeMatrix->triple[index - 1].value = tsMatrix->triple[i].value;
//该列的一个元素已经有一个放进去 位置往后移一个		
		postion[tsMatrix->triple[i].col]++;    //当前列的开始位置加1
	}

	free(postion);
	free(nums);

	return transposeMatrix;
}

 

 

转置的测试

完整的代码:

SparseMatrix.c

#include<stdio.h>
#include<malloc.h>

#include"SparseMatrix.h"

//一次快速定位转置 总体分三步
//1、先对原三元表中的元素统计 每列有多少个非0元素,也就是转置完B的每行有多少个非0元素
//2、通过第一步计算B矩阵每一行元素在三元表中第几个开始
//3、把三元表A的元素直接放到B的三元表里面去 一次遍历三元表A防止B中去
TSMATRIX *transposeSparseMatrix_3(const TSMATRIX *tsMatrix) {
	int *nums = NULL;
	int *postion = NULL;
	int i;
	TSMATRIX *transposeMatrix = NULL;

//对转转置后的三元表初始化申请空间	
	initSparseMatrix(&transposeMatrix, tsMatrix->matrix_col, tsMatrix->matrix_row, tsMatrix->num_count);
//申请一个记录元素出现位置的矩阵 长度和原稀疏矩阵列保持一致	
	postion = (int *)calloc(sizeof(int), tsMatrix->matrix_col);
	nums = (int *)calloc(sizeof(int), tsMatrix->matrix_col);
	for(i = 0; i < tsMatrix->num_count; i++) {
//遍历原三元表,统计矩阵的每一列有多少个非零元素 
//对于矩阵
//0 14 0 0  -5
//0 -7 0 0   0
//36 0 0 28  0
//每一列包含的元素个数为 1 2 0 1 1		
		nums[tsMatrix->triple[i].col]++;
	}
	for(i = 1, postion[0]= 1; i < tsMatrix->matrix_col; i++) {
//通过上一步计算的矩阵中每一列有多少个非零元素 计算矩阵的每一列元素在三元表出现的位置
//即是1 2 4 4 5	
		postion[i] = nums[i - 1] + postion[i - 1];
	}
	

	for(i = 0; i < tsMatrix->num_count; i++) {
//原三元表当前这一项在转置的三元表中应该出现的位置		
		int index = postion[tsMatrix->triple[i].col];
		transposeMatrix->triple[index - 1].row = tsMatrix->triple[i].col;
		transposeMatrix->triple[index - 1].col = tsMatrix->triple[i].row;
		transposeMatrix->triple[index - 1].value = tsMatrix->triple[i].value;
//该列的一个元素已经有一个放进去 位置往后移一个		
		postion[tsMatrix->triple[i].col]++;    //当前列的开始位置加1
	}

	free(postion);
	free(nums);

	return transposeMatrix;
}

//按照列下标递增的顺序去找tsMatrix->triple[j].col == i
//在三元表里面当前元素的列等于i就找到一个
TSMATRIX *transposeSparseMatrix_1(const TSMATRIX *tsMatrix) {
	int i;
	int j;
	int index = 0;
	TSMATRIX *transposeMatrix = NULL;

	initSparseMatrix(&transposeMatrix, tsMatrix->matrix_col, tsMatrix->matrix_row, tsMatrix->num_count);
	for(i = 0; i < tsMatrix->matrix_col; i++) {
		for(j = 0; j < tsMatrix->num_count; j++) {
			if(tsMatrix->triple[j].col == i) {
				transposeMatrix->triple[index].row = tsMatrix->triple[j].col;
				transposeMatrix->triple[index].col = tsMatrix->triple[j].row;
				transposeMatrix->triple[index++].value = tsMatrix->triple[j].value;
			}	
		}
		if(index >= tsMatrix->num_count) {   //如果发现此时三元表已经填满就可以不用再进行外层循环了
			break;    
		}
	}

	return transposeMatrix;
}

void showThreeTable(const TSMATRIX *tsMatrix) {
	int i;

	for(i = 0; i <tsMatrix->num_count; i++) {
		printf("(%d,%d,%d)\n", 
			tsMatrix->triple[i].row,
			tsMatrix->triple[i].col, 
			tsMatrix->triple[i].value);
	}
}

void showSparseMatrix(const TSMATRIX *tsMatrix) {
	int i;
	int j;
	int index = 0;

	for(i = 0; i < tsMatrix->matrix_row; i++) {
		for(j = 0; j < tsMatrix->matrix_col; j++) {
			if(tsMatrix->triple[index].row == i &&
			 tsMatrix->triple[index].col == j) {
				printf("%3d", tsMatrix->triple[index++].value);
			}else {
				printf("%3d", 0);
			}
		}
		puts("");
	}
	puts("");
}
void insertDataToMatrix(TSMATRIX *tsMatrix)  {
	int i;
	int row;
	int col;
	int value;

	printf("Insert data by row and then col ascending(row, col, value)\n");
	for(i = 0; i < tsMatrix->num_count; i++) {
		printf("[%d/%d] data: ", i+1, tsMatrix->num_count);
		scanf("%d%d%d", &row, &col, &value);
		tsMatrix->triple[i].row = row;
		tsMatrix->triple[i].col = col;
		tsMatrix->triple[i].value = value;
	}
}

void destorySparseMatrix(TSMATRIX **tsMatrix) {
	if(*tsMatrix == NULL) {
		return;
	}

	free((*tsMatrix)->triple);
	free(*tsMatrix);
	*tsMatrix = NULL;
}

void initSparseMatrix(TSMATRIX **tsMatrix, int matrix_row, int matrix_col, int num_count) {
	TSMATRIX *tp;

	if(NULL != (*tsMatrix) || (matrix_row*matrix_col < num_count)
		|| matrix_col <= 0 || matrix_row <= 0) {

		printf("ERROR INPUT DATA\n");
		return;
	}

	tp = (TSMATRIX *)malloc(sizeof(TSMATRIX));

	if(tp != NULL) {  //有没有分配到空间怎么说
		tp->triple = (TRIPLE *)calloc(sizeof(TRIPLE), num_count);
		tp->matrix_row = matrix_row;
		tp->matrix_col = matrix_col;
		tp->num_count = num_count;
		*tsMatrix = tp;
	}
}

SparseMatrix.h

Demo.c

#ifndef _SPARSE_MATRIX_H
#define _SPARSE_MATRIX_H

typedef struct  TRIPLE{
	int row;
	int col;
	int value;
}TRIPLE;

typedef struct TSMATRIX {
	TRIPLE *triple;	
	int matrix_row;
	int matrix_col;
	int num_count;    //这个相当于triple数组的长度
}TSMATRIX;

void initSparseMatrix(TSMATRIX **tsMatrix, int matrix_row, int matrix_col, int num_count);
void destorySparseMatrix(TSMATRIX **tsMatrix);
void showSparseMatrix(const TSMATRIX *tsMatrix);
void insertDataToMatrix(TSMATRIX *tsMatrix);
//对一个矩阵进行转置
TSMATRIX *transposeSparseMatrix_1(const TSMATRIX *tsMatrix);

//一次定位转置 较前两种转置降低时间复杂度对一个矩阵进行转置
TSMATRIX *transposeSparseMatrix_3(const TSMATRIX *tsMatrix);

//打印矩阵的三元表
void showThreeTable(const TSMATRIX *tsMatrix);

#endif

#include<stdio.h>

#include"SparseMatrix.h"

int main(void) {
	TSMATRIX *tsMatrix = NULL;
	TSMATRIX *transposeMatrix = NULL;
	int matrix_col;
	int matrix_row;
	int num_count;

	printf("input row col and num_count: ");
	scanf("%d%d%d", &matrix_row, &matrix_col, &num_count);

	initSparseMatrix(&tsMatrix, matrix_row, matrix_col, num_count);
	insertDataToMatrix(tsMatrix);
	showSparseMatrix(tsMatrix);
	transposeMatrix = transposeSparseMatrix_3(tsMatrix);
	showSparseMatrix(transposeMatrix);

	destorySparseMatrix(&tsMatrix);
	destorySparseMatrix(&transposeMatrix);

	return 0;
}
/*
4 5 9
0 0 1
0 2 3
0 3 4
1 1 2
1 3 1
2 2 3
2 4 1
3 1 1
3 3 1

3 5 5
0 1 14
0 4 -5
1 1 -7
2 0 36
2 3 28
*/

 

 

 

 

  • 10
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
稀疏矩阵是指其中大部分元素为零的矩阵。由于大多数矩阵都是稠密的,即大多数元素都不为零,因此通常情况下,我们使用一个二维数组来示一个矩阵。但是,对于稀疏矩阵来说,这种方法会造成很大的浪费,因为大量的空间被用来存储零元素。为了解决这个问题,我们可以使用稀疏矩阵三元示法。 稀疏矩阵三元示法是将稀疏矩阵中每个非零元素的行、列和值存储在一个三元组中。其数据结构如下所示: ``` struct Triple{ int row, col; double value; }; ``` 其中,row示非零元素所在的行,col示非零元素所在的列,value示非零元素的值。我们可以使用一个数组来存储所有的非零元素,这个数组就是稀疏矩阵三元组。 稀疏矩阵三元示法的优点是它可以节省存储空间,缺点是它不方便进行矩阵运算。因此,在进行矩阵运算时,我们需要将稀疏矩阵转换成其他更方便进行矩阵运算的示方法,如压缩矩阵和坐标矩阵等。 对于稀疏矩阵的求解,可以使用稀疏矩阵三元示法结合三元组高斯消元算法来进行求解。三元组高斯消元算法是一种针对稀疏矩阵的高斯消元算法,其基本思想是将矩阵化为上三角矩阵或下三角矩阵,然后通过回代或者前代求解方程。由于矩阵中大部分元素为零,因此在进行高斯消元时,我们只需要考虑非零元素,这样可以大大提高计算效率。 三元组高斯消元算法的基本步骤如下: 1. 将稀疏矩阵转换成三元示法; 2. 对三元组按照行和列的顺序进行排序; 3. 从第一个非零元素开始,进行高斯消元,将矩阵化为上三角矩阵或下三角矩阵; 4. 通过回代或者前代求解方程。 具体实现可以参考以下代码: ``` void SparseTripletGaussElimination(SparseTriplet& triplet, vector<double>& b) { int n = triplet.rows; vector<Triple> A(triplet.data, triplet.data + triplet.num); sort(A.begin(), A.end(), [](const Triple& a, const Triple& b){ return a.row < b.row || (a.row == b.row && a.col < b.col); }); vector<int> row(n+1), col(triplet.num), diag(n); vector<double> val(triplet.num); for (int i = 0; i < triplet.num; i++) { row[A[i].row]++; } for (int i = 1; i <= n; i++) { row[i] += row[i-1]; } for (int i = 0; i < triplet.num; i++) { int r = A[i].row, c = A[i].col; double v = A[i].value; int k = row[r]++; // 获取 r 行中下一个非零元素的位置 col[k] = c; val[k] = v; if (r == c) { diag[r] = k; // 记录对角线元素的位置 } } for (int k = 0; k < n-1; k++) { if (val[diag[k]] == 0) { // 对角线元素为零,无法消元 throw runtime_error("zero pivot encountered"); } for (int i = diag[k]+1; i < row[k+1]; i++) { int r = col[i]; double factor = val[i] / val[diag[k]]; for (int j = diag[k]+1; j < row[k+1]; j++) { if (col[j] == r) { val[j] -= factor * val[diag[k]]; } } b[r] -= factor * b[k]; } } if (val[diag[n-1]] == 0) { // 对角线元素为零,无法消元 throw runtime_error("zero pivot encountered"); } for (int k = n-1; k >= 0; k--) { double sum = 0; for (int i = diag[k]+1; i < row[k+1]; i++) { sum += val[i] * b[col[i]]; } b[k] = (b[k] - sum) / val[diag[k]]; } } ``` 其中,SparseTriplet是稀疏矩阵三元示法的数据结构,b是待求解的方程的右侧向量。在实现中,我们首先将三元组按照行和列的顺序进行排序,然后将其转换成压缩矩阵的形式,接着进行高斯消元,并通过回代或者前代求解方程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值