数据结构与算法 & 稀疏矩阵 & 三元组顺序表

稀疏矩阵

    • 一、稀疏矩阵(sparse matrix)
      • 1.定义(百度):
      • 2.表示方法
      • 3.稀疏矩阵三元组顺序表法
    • 二、稀疏矩阵----三元法实现
      • 1.三元组的初始化与销毁
      • 2.稀疏矩阵的录入与显示
      • 3.简单调试
      • 4.稀疏矩阵的转置
    • 三、总结

一、稀疏矩阵(sparse matrix)

1.定义(百度):

矩阵中非零元素的个数远远小于矩阵元素的总数,并且非零元素的分布没有规律, 通常认为矩阵中非零元素的总数比上矩阵所有元素总数的值小于等于0.05时,则称该矩阵为稀疏矩阵(sparse matrix),该比值称为这个矩阵的稠密度
数据结构与算法 & 稀疏矩阵 & 三元组顺序表_第1张图片

2.表示方法

  1. 三元组顺序表
  2. 行逻辑连接的顺序表
  3. 十字链表

本文讲的是三元组顺序表

3.稀疏矩阵三元组顺序表法

行序\ \列序 0 1 2 3 4 5
0 0 0 0 3 0 0
1 0 0 1 0 1 0
2 0 0 0 0 0 6
3 0 1 0 4 0 0

如上表,这是一个简单的稀疏矩阵。为4行 * 6 列的矩阵。
当然,行序,列序是从 0 开始。
三元组顺序表,有三个元素,分为行序/ 列序/ 数值 。且·,三元组只针对非0数值。
所以,使用三元组顺序表法后,该稀疏矩阵表示为:
数据结构与算法 & 稀疏矩阵 & 三元组顺序表_第2张图片
注意: 三元组顺序表,一定要注意顺序。行序在前,列序在后,按行排序,行序同,列序排。
所以,三元组的数据结构可以这样:

//三元组顺序表存储数据
typedef struct TRIPLE {
	int row;//行序
	int col;//列序
	int value;//值
}TRIPLE;

控制三元组可以有这几个元素:

//三元组法控制头
typedef struct SPARSE_MATRIX {
	TRIPLE *triple;//三元组表
	int maxRow;//最大行序
	int maxCol;//最大列序
	int count;  //有效元素个数
}SPARSE_MATRIX;

二、稀疏矩阵----三元法实现

1.三元组的初始化与销毁

//三元组初始化
boolean initSparseMatrix(SPARSE_MATRIX **s_m, int maxRow, int maxCol, int count) {
 	if (NULL == s_m || NULL != *s_m 
 			|| maxRow <= 0 || maxCol <= 0 || count <= 0) {
 		return FALSE;
 	}
 	(*s_m) = (SPARSE_MATRIX *) calloc(sizeof(SPARSE_MATRIX), 1);
 	if (NULL == *s_m) {
 		return FALSE;
 	}
 	(*s_m)->triple = (TRIPLE *) calloc(sizeof(TRIPLE), count);
 	if ((*s_m)->triple == NULL) {
 		free(*s_m);
 		*s_m = NULL;
 		return FALSE;
 	}
 	(*s_m)->maxRow = maxRow;
 	(*s_m)->maxCol = maxCol;
 	(*s_m)->count = count;

 	return TRUE;
}
//销毁
void destorySparseMatrix(SPARSE_MATRIX **s_m) {
	if (NULL == s_m ||NULL == *s_m) {
		return;
	}
	free((*s_m)->triple);
	free(*s_m);
	*s_m = NULL;	
}

2.稀疏矩阵的录入与显示

录入

//数据录入
boolean enterSparseMatrix(SPARSE_MATRIX *s_m) {
	int index;
	int row;
	int col;
	int value;

 	if (NULL == s_m) {
 		return FALSE;
 	}
 	//按序号录入
	for (index = 0; index < s_m->count; index++) {
		printf("请输入三元组数据(行 列 值):" );
		scanf("%d %d %d", &row, &col, &value);
		(s_m)->triple[index].row = row;
		(s_m)->triple[index].col = col;
		(s_m)->triple[index].value = value;
	}

	return TRUE;
}

显示:
采用的是,从矩阵第一个元素到最后一个元素,依次显示。如果有行列同于三元组行序,列序,显示真正的值。

//显示三元组
void showSparseMatrix(const SPARSE_MATRIX *s_m) {
	int row;
	int col;
	int index = 0;
	int value = 0;

	for (row = 0; row < s_m->maxRow; row++) {
		for (col = 0; col < s_m->maxCol; col++) {
			if (col == s_m->triple[index].col 
				&& row == s_m->triple[index].row) {
				//当行列值与三元组中数据的行序,列序相同时,复制
				value = s_m->triple[index++].value;
			} else {
				value = 0;
			}
			printf("%5d", value);
		}
		printf("\n");
	}
}

3.简单调试

test.c

#include 

#include "sparseMatrix.h"
int main() {
	int maxRow;
	int maxCol;
	int count;
	SPARSE_MATRIX *smOne = NULL;//申请第一个三元组
	//SPARSE_MATRIX *smTwo = NULL;//申请第二个三元组
	
	printf("请输入稀疏矩阵行阶、列阶和有效元素个数:");
	scanf("%d %d %d", &maxRow, &maxCol, &count);
	initSparseMatrix(&smOne, maxRow, maxCol, count);
	enterSparseMatrix(smOne);

	showSparseMatrix(smOne);
	//smTwo = revangeMatrix(smOne);//转置
	//printf("转置后:\n");
	//showSparseMatrix(smTwo);

	destorySparseMatrix(&smOne);
	//destorySparseMatrix(&smTwo);
	return 0;
}

powershell 运行方法后面再说。
这里主要讲一个问题:有关内存泄漏
我们知道,初始化时需要修改三元组控制头的数据,而且由于函数中局部变量修改,主函数的实参和子函数中的形参是值传递关系,但是两者数据存储空间不同,所以想通过函数来修改主函数中局部变量需要使用指针,但为啥还需要两层指针?
这里的主要目的是防止使用者多次初始化同一个稀疏矩阵;

initSparseMatrix(smOne, maxRow, maxCol, count);  //使用一层指针smOne
initSparseMatrix(smOne, maxRow, maxCol, count);

如果两次初始化的话,就会使第一次初始化的空间丢失了指针,无法再进行访问,造成内存泄漏。
而如果使用两层指针,可以使第二次初始化无法修改smOne的值。实现控制头指针的保护和防止内存泄漏。

4.稀疏矩阵的转置

转置:简单来说就是行序变成新的矩阵列序,列序变成新的矩阵行序。
但是,由于三元组的数据存储是有一定顺序的,可以完成转置,但是不能直接输出。

原三元组       转置后			正确的顺序
0 3 3         3 0 3             1 3 1
1 2 1         2 1 1				2 1 1
1 4 1		  4 1 1				3 0 3
2 5 6		  5 2 6 			3 3 4 
3 1 1		  1 3 1				4 1 1
3 3 4         3 3 4				5 2 6

那么该采用什么方法实现排序呢?
首先明确排序规则,行序在前,列序在后,值不影响排顺序。
思路1: 采用直接交换排序的效率并不高,由于有双元素控制,所以直接交换排序得考虑两者最高最小,效率低。

思路2: 使用基数排序,以原来的列序为“桶”,扫描三元组表,根据行序大小排定桶内顺序,接着扫描各桶依次排序。
未实现原因如下:
桶的大小范围不确定。且桶很多。稀疏矩阵其重复数据多,也意味着值其行列值很大。使用数组不知范围,使用链表又太杂。

思路3: 可以扫描三元表,得到原矩阵每一列有多少有效数据。转置后,这一数据就是新三元组每一行拥有多少数据,那么就可以确定,转置后每一行第一个元素在三元组顺序表的序号。
数组(下标对应行序)保存该序号,扫描原三元组表,可以将其他数据根据列序(转置后为行序),找到对应数组下标值,再将该数据存入新三元组表指导位置。同时该行元素个数(数组某值)+1;
数据结构与算法 & 稀疏矩阵 & 三元组顺序表_第3张图片再梳理一遍:

  1. 扫描三元组,得到每一行有效元素个数
  2. 累加求和,得到每一行前有效元素个数
  3. 在扫描三元组,根据列序,该行前有效个数确定在新三元组的位置,同时该行前有效个数加1
  4. 构建好新的三元组

其实这挺像基数排序的。不过它不存储排序的值,只是确定位置。这个方法可以成功一个重要原因是,本来的三元组表就是一个顺序表,其行序就已经排序好,这意味着转序的后的同行的列序顺序排好了。所以可以元素个数加一。
代码实现:

//得到每行有效元素个数
static void getReplaceRowCount(int *array, const SPARSE_MATRIX *s_m) {
	int index;
	int i;

	for (index = 0; index < s_m->count; index++) {
		++array[s_m->triple[index].col + 1];
	}
}
//得到每行之前的元素总数
static void rollAdd(int *array, int count) {
	int index;
	int i;

	for (index = 1; index < count; index++) {
		array[index + 1] += array[index];
	}
}
//根据位置表实现转置后的三元组表构建
static void revange(const SPARSE_MATRIX *s_m, SPARSE_MATRIX *targetMatrix) {
	int *indexArray = NULL;
	int count = s_m->maxCol + 1;
	int index;
	int t;

	indexArray = (int *) calloc(sizeof(int), count);
	getReplaceRowCount(indexArray, s_m);
	rollAdd(indexArray, count);

	for (index = 0; index < s_m->count; index++) {
		t = s_m->triple[index].col;
		targetMatrix->triple[indexArray[t]].row = s_m->triple[index].col; 
		targetMatrix->triple[indexArray[t]].col = s_m->triple[index].row; 
		targetMatrix->triple[indexArray[t]].value = s_m->triple[index].value; 
		indexArray[t]++;//元素加一
	}

	free(indexArray);
}
//转置
SPARSE_MATRIX *revangeMatrix(const SPARSE_MATRIX *s_m) {
	SPARSE_MATRIX *result = NULL;

	initSparseMatrix(&result, s_m->maxCol, s_m->maxRow, s_m->count);
	revange(s_m, result);

	return result;
}

运行检测:
运行方法(cl):

  1. cl sparseMatrix.c
  2. cl test.c sparseMatrix.obj
  3. .\test

数据结构与算法 & 稀疏矩阵 & 三元组顺序表_第4张图片

三、总结

1.数组玩的就是下标。
2.先理清思路,多手工过几遍。再上手编。
3.编的时候,有机会就多调试。一来可以确保自己程序根据自己思路走,二来可以检查前面的错误,防止错误累积。这就是反馈!!!


笔者水平有限,目前只能描述以上问题,如果有其他情况,可以留言,有错误,请指教,有继续优化的,请分享,谢谢!
本篇文章是否有所收获?阅读是否舒服?又什么改进建议?希望可以给我留言或私信,您的的分享,就是我的进步。谢谢。


完整代码如下: **sparseMatrix.h**
#ifndef _SL_SPARES_MATRIX_H_
#define _SL_SPARES_MATRIX_H_

typedef unsigned char boolean;
#define TRUE   1
#define FALSE  0

typedef struct TRIPLE {
	int row;//行序
	int col;//列序
	int value;//值
}TRIPLE;

typedef struct SPARSE_MATRIX {
	TRIPLE *triple;//三元组表
	int maxRow;//最大行序
	int maxCol;//最大列序
	int count;  //有效元素个数
}SPARSE_MATRIX;

boolean initSparseMatrix(SPARSE_MATRIX **s_m, int maxRow, int maxCol, int count);
void destorySparseMatrix(SPARSE_MATRIX **s_m);
boolean enterSparseMatrix(SPARSE_MATRIX *s_m);
void showSparseMatrix(const SPARSE_MATRIX *s_m);
SPARSE_MATRIX *revangeMatrix(const SPARSE_MATRIX *s_m);

#endif 

test.c

#include 

#include "sparseMatrix.h"

int main() {
	int maxRow;
	int maxCol;
	int count;
	SPARSE_MATRIX *smOne = NULL;//申请第一个三元组
	SPARSE_MATRIX *smTwo = NULL;//申请第二个三元组
	
	printf("请输入稀疏矩阵行阶、列阶和有效元素个数:");
	scanf("%d %d %d", &maxRow, &maxCol, &count);
	initSparseMatrix(&smOne, maxRow, maxCol, count);
	enterSparseMatrix(smOne);

	showSparseMatrix(smOne);
	smTwo = revangeMatrix(smOne);//转置
	printf("转置后:\n");
	showSparseMatrix(smTwo);

	destorySparseMatrix(&smOne);
	destorySparseMatrix(&smTwo);

	return 0;
}

sparseMatrix.c

#include 
#include 

#include "sparseMatrix.h"
//三元组初始化
boolean initSparseMatrix(SPARSE_MATRIX **s_m, int maxRow, int maxCol, int count) {
 	if (NULL == s_m || NULL != *s_m 
 			|| maxRow <= 0 || maxCol <= 0 || count <= 0) {
 		return FALSE;
 	}
 	(*s_m) = (SPARSE_MATRIX *) calloc(sizeof(SPARSE_MATRIX), 1);
 	if (NULL == *s_m) {
 		return FALSE;
 	}
 	(*s_m)->triple = (TRIPLE *) calloc(sizeof(TRIPLE), count);
 	if ((*s_m)->triple == NULL) {
 		free(*s_m);
 		*s_m = NULL;
 		return FALSE;
 	}
 	(*s_m)->maxRow = maxRow;
 	(*s_m)->maxCol = maxCol;
 	(*s_m)->count = count;

 	return TRUE;
}
//销毁
void destorySparseMatrix(SPARSE_MATRIX **s_m) {
	if (NULL == s_m ||NULL == *s_m) {
		return;
	}
	free((*s_m)->triple);
	free(*s_m);
	*s_m = NULL;	
}
//数据录入
boolean enterSparseMatrix(SPARSE_MATRIX *s_m) {
	int index;
	int row;
	int col;
	int value;

 	if (NULL == s_m) {
 		return FALSE;
 	}
 	//按序号录入
	for (index = 0; index < s_m->count; index++) {
		printf("请输入三元组数据(行 列 值):" );
		scanf("%d %d %d", &row, &col, &value);
		(s_m)->triple[index].row = row;
		(s_m)->triple[index].col = col;
		(s_m)->triple[index].value = value;
	}

	return TRUE;
}
//显示三元组
void showSparseMatrix(const SPARSE_MATRIX *s_m) {
	int row;
	int col;
	int index = 0;
	int value = 0;

	for (row = 0; row < s_m->maxRow; row++) {
		for (col = 0; col < s_m->maxCol; col++) {
			if (col == s_m->triple[index].col 
				&& row == s_m->triple[index].row) {
				//当行列值与三元组中数据的行序,列序相同时,复制
				value = s_m->triple[index++].value;
			} else {
				value = 0;
			}
			printf("%5d", value);
		}
		printf("\n");
	}
}
//得到每行有效元素个数
static void getReplaceRowCount(int *array, const SPARSE_MATRIX *s_m) {
	int index;
	int i;

	for (index = 0; index < s_m->count; index++) {
		++array[s_m->triple[index].col + 1];
	}
}
//得到每行之前的元素总数
static void rollAdd(int *array, int count) {
	int index;
	int i;

	for (index = 1; index < count; index++) {
		array[index + 1] += array[index];
	}
}
//根据位置表实现转置后的三元组表构建
static void revange(const SPARSE_MATRIX *s_m, SPARSE_MATRIX *targetMatrix) {
	int *indexArray = NULL;
	int count = s_m->maxCol + 1;
	int index;
	int t;

	indexArray = (int *) calloc(sizeof(int), count);
	getReplaceRowCount(indexArray, s_m);
	rollAdd(indexArray, count);

	for (index = 0; index < s_m->count; index++) {
		t = s_m->triple[index].col;
		targetMatrix->triple[indexArray[t]].row = s_m->triple[index].col; 
		targetMatrix->triple[indexArray[t]].col = s_m->triple[index].row; 
		targetMatrix->triple[indexArray[t]].value = s_m->triple[index].value; 
		indexArray[t]++;
	}

	free(indexArray);
}
//转置
SPARSE_MATRIX *revangeMatrix(const SPARSE_MATRIX *s_m) {
	SPARSE_MATRIX *result = NULL;

	initSparseMatrix(&result, s_m->maxCol, s_m->maxRow, s_m->count);
	revange(s_m, result);

	return result;
}

2020年03.11 家

你可能感兴趣的:(数据结构,数据结构,矩阵)