看数据结构写代码(20)稀疏矩阵(顺序存储方式)

当矩阵 的 有用信息非常少时,我们考虑将矩阵压缩存储。这就涉及到 特殊矩阵 和 稀疏矩阵。

特殊矩阵 指的是 有一定规律的 矩阵,这个矩阵 我们 只存储 部分 有用信息,其余的信息 可以通过 公式 转换 求得。例如 对称矩阵,我们按行存储主对角线以下(包括主对角线)的元素,其余元素 我们可以通过 下面的公式求得。

稀疏矩阵,指的事没有一定规律的矩阵,并且  有用信息总数/矩阵总数 小于等于 0.05 的时候,我们称之为 稀疏矩阵。

下面的代码,给出了 稀疏矩阵的 “行逻辑链接的顺序存储” 方式 实现的 矩阵 快速转置 以及 矩阵相乘的 代码。

欢迎指出 代码 bug,下面上代码

// Sparse Matrix.cpp : 定义控制台应用程序的入口点。
//
//稀疏矩阵的顺序存储方式实现(行逻辑链接数据表)

#include "stdafx.h"

#define MATRIX_MAX_SIZE	10000
#define MATRIX_MAX_ROW	500
enum E_State
{
	E_State_Error = 0,
	E_State_Ok = 1,
};
typedef int ElementType;

//矩阵元素结构
struct MatrixData
{
	int row;//行
	int col;//列
	ElementType data;//数据
};

struct SMatrix
{
	MatrixData base[MATRIX_MAX_SIZE];
	int rPos[MATRIX_MAX_ROW];//存储每行第一个元素在数组中的位置,并且保留了 最后一行的 边界值
	int rowNum;//行数
	int colNum;//列数
	int totalNum;//总数
};

void SMatrixInit(SMatrix * matrix,int * base,int row,int col){
	matrix->rowNum = row;
	matrix->colNum = col;
	int totalNum = 0;
	for (int i = 0; i < row; i++)
	{
		int lastRowTotal = totalNum;//记录从第一行到 上一行的总数
		for (int j = 0; j < col; j++)
		{
			ElementType data = base[i*col+j];
			if (data != 0)
			{
				matrix->base[totalNum].row = i;
				matrix->base[totalNum].col = j;
				matrix->base[totalNum].data = data;
				totalNum++;
			}
		}
		matrix->rPos[i] = lastRowTotal;
	}
	matrix->rPos[row] = totalNum;//为了 矩阵相乘,添加 边界条件
	matrix->totalNum = totalNum;
}

//普通转置 时间复杂度 O(m.colNum * m.totalNum)
//遍历矩阵m,将第一列到 最后 一列 的矩阵元素加入到 t矩阵中

void SMatrixTranspose(SMatrix m,SMatrix * t){
	t->totalNum = m.totalNum;
	t->rowNum = m.colNum;
	t->colNum = m.rowNum;
	int total = 0;
	for (int i = 0; i < m.colNum; i++)
	{
		for (int j = 0; j < m.totalNum; j++)
		{
			MatrixData data = m.base[j];
			if (data.col == i)
			{
				t->base[total].row = data.col;
				t->base[total].col = data.row;
				t->base[total].data = data.data;
				total++;
			}
		}
	}

}

//快速转置 时间复杂度为 O(m.totalNum + m.colNum)
void SMatrixQuickTranspose(SMatrix m,SMatrix * t){
	t->totalNum = m.totalNum;
	t->rowNum = m.colNum;
	t->colNum = m.rowNum;
	if (m.totalNum == 0)
	{
		return;
	}
	int col[MATRIX_MAX_ROW] = {0};
	for (int i = 0; i < m.totalNum; i++)//计算矩阵m 每一列 的 元素个数
	{
		MatrixData data = m.base[i];
		col[data.col+1]++;
	}
	//计算 矩阵M 每一列 起始元素 在数组中的位置
	for (int i = 1; i < m.colNum; i++)
	{
		col[i] = col[i-1] + col[i];
	}
	for (int i = 0; i < m.totalNum; i++)
	{
		MatrixData data = m.base[i];
		int colBase = col[data.col];
		t->base[colBase].col = data.row;
		t->base[colBase].row = data.col;
		t->base[colBase].data = data.data;
		col[data.col]++;
	}
	//最后设置 每行 首元素地址
	t->rPos[0] = 0;
	for (int i = 1; i < t->rowNum; i++)
	{
		t->rPos[i] = col[i-1];
	}

}

//矩阵相乘
E_State SMatrixMult(SMatrix  m1,SMatrix  m2,SMatrix * result){
	if (m1.colNum != m2.rowNum)//排除不合法的情况..
	{
		return E_State_Error;
	}
	result->rowNum = m1.rowNum;
	result->colNum = m2.colNum;
	result->totalNum = 0;
	if (m1.totalNum * m2.totalNum != 0)
	{
		for (int m1Row = 0; m1Row < m1.rowNum; m1Row++)
		{
			int m1End = m1.rPos[m1Row+1];
			int colCount[MATRIX_MAX_ROW] = {0};
			for (int m1Start = m1.rPos[m1Row]; m1Start < m1End; m1Start++)
			{
				MatrixData m1Data = m1.base[m1Start];
				int col = m1Data.col;
				for (int m2start = m2.rPos[col]; m2start < m2.rPos[col+1]; m2start++)
				{
					MatrixData m2Data = m2.base[m2start];
					colCount[m2Data.col] += m1Data.data * m2Data.data;
				}
			}
			result->rPos[m1Row] = result->totalNum;
			for (int col = 0; col < m2.colNum; col++)
			{
				if (colCount[col] != 0)
				{
					result->base[result->totalNum].col = col;
					result->base[result->totalNum].row = m1Row;
					result->base[result->totalNum].data = colCount[col];
					result->totalNum ++;
				}
			}
		}
		result->rPos[result->rowNum] = result->totalNum;
	}
	return E_State_Ok;
}

//遍历矩阵
void SMatricTraverse(SMatrix matrix){
	int rowNum = 0;
	printf("--------------遍历开始------------------------\n");
	for (int i = 0; i < matrix.totalNum; i++)
	{
		MatrixData data = matrix.base[i];
		printf("%d行  %d列 : %d\n",data.row+1,data.col+1,data.data);
	}
	printf("--------------遍历结束------------------------\n");
}

int initData[5][10] = {
	{1,0,0,0,0,0,0,0,0,0},
	{0,0,2,0,0,0,0,0,5,0},
	{0,0,0,3,0,0,0,0,0,0},
	{0,2,0,0,0,0,0,0,0,0},
	{1,0,0,0,0,0,0,0,0,9}
};

int initData2 [10][2] = {
	{1,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,6},
	{0,0},
	{0,0},
	{5,0},
	{0,0},
};

int _tmain(int argc, _TCHAR* argv[])
{
	SMatrix matrix;
	SMatrixInit(&matrix,(int *)initData,5,10);
	SMatricTraverse(matrix);
	printf("--------------普通转置-----------------------\n");
	SMatrix tMatrix;
	SMatrixTranspose(matrix,&tMatrix);
	SMatricTraverse(tMatrix);
	printf("--------------快速转置-----------------------\n");
	SMatrix qtMatrix;
	SMatrixQuickTranspose(matrix,&qtMatrix);
	SMatricTraverse(qtMatrix);
	printf("--------------矩阵相乘-----------------------\n");
	SMatrix m2;
	SMatrixInit(&m2,(int *)initData2,10,2);
	SMatrix mul;
	SMatrixMult(matrix,m2,&mul);
	SMatricTraverse(mul);
	return 0;
}

运行代码,截图

时间: 2024-08-29 05:46:39

看数据结构写代码(20)稀疏矩阵(顺序存储方式)的相关文章

看数据结构写代码(21) 稀疏矩阵(十字链表方式)

写完 这个样例,花费了 我不少时间.大部分时间 花费在 调试 内存问题上. 比如在销毁十字链表时.多次释放节点空间,造成 _CrtIsValidHeapPointer(pUserData) 异常. 当使用malloc 分配 一个 空间时,会将这个空间的起始地址和长度 加到一个链表中去.free(p)的时候 ,会从 链表里 查找 是否 有 这个地址空间,找到了就将这个节点从链表中删除._CrtIsValidHeapPointer(pUserData)  这个函数 正是 检查 这个空间是否 在链表里

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(67) 置换 _ 选择排序(完结篇)

杂谈: 严蔚敏版<数据结构(C语言版)> 一书 终于看完了.这是 一个完结,也是 一个新的开端.<算法导论> 已到手. 置换选择排序的思想 是 将 归并段 尽量 变的 更大,而不是根据 内存 大小 限制在 固定的 大小. 这样 可以 利用赫夫曼树 来 进行 最优归并树,从而 使 外存 读写次数 最少. 下面给出 具体 代码:欢迎指出代码不足. // Replace_Selcetion.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q

看数据结构写代码(35) 图的邻接矩阵表示法

杂谈:最近清明小长假,好好的放松了一下.节前 和 节后 都有点 松懈.不好,不好.贵在坚持.加油. 图的邻接矩阵表示法是用 两个数组 来表示 图的数据结构.一个是顶点数组,另一个是邻接矩阵数组.邻接矩阵 里存放着 顶点的关系. 用邻接矩阵表示图,在 看 顶点之间 是否有边,或者 求顶点的度等操作时比较简单.但空间浪费巨大,在插入,删除 顶点 和边 操作时 需要 移动大量数据,造成不便.所以在插入删除比较多,节点数比较多的时候 不宜 使用这种结构. 下面上代码: 源代码网盘地址:点击打开链接 //

看数据结构写代码(50)伙伴系统

伙伴系统 是一种 只 可以 分配 2的 幂次方 个 空间的 ,回收 内存 时 只 合并 "伙伴空间" 的一种 动态内存管理方式. 例如 一个 空间 大小 为 64 的 内存,伙伴 系统 为 这 64 的内存  建立 一组 双向循环 链表,分别 管理着  2的 0 次方,2的1 次方幂,2的 2 次方幂...2的6次方幂的 可用空间. 即使 我们 只想分配 一个 大小 为3的 空间,系统 却 只能 返回 一个 内存 大小 为 4(2的2次方)的 一个空间. 系统 在 初始化的 时候 ,并

看数据结构写代码(15)链式队列的实现

队列 和 栈 是 一种 受限制的 线性表.所以 他们的 实现方式 都 相差 无几.之前有过  链栈 和 链式线性表 的 实现经验,自然 写 链队 ,也毫无问题. 下面详细讲解每一段代码 的技术要点 下面是队列节点的数据结构 struct QueueNode { ElementType data; QueueNode * next; }; //生成一个节点 QueueNode * queueNodeMake(ElementType data){ QueueNode * pNode = (Queue

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环

看数据结构写代码(40) 无向图的深度优先生成树与广度优先生成树

图的深度优先遍历 和 广度 优先 遍历 算法中的 每一次 最外层 循环 都 产生 一个 无向图 的 连通分量,每一个连通分量,都可以产生一个生成树,将这些生成树合在 一起 就是 一个 森林. 用 树的 孩子 兄弟 链表 表示法 来 表示 这个 森林, 就是 这一节 算法的  内容. 深度优先森林 代码 : //深度优先生成森林 void dfsTree(AMLGraph g,int i,Tree * t,bool isVisited[]){ isVisited[i] = true; bool i

看数据结构写代码(66) 败者树

计算机的 内存 是 有限的,无法 存入 庞大的数据.当 遇到 大数据需要排序时,我们 需要 将 这些 数据 分段 从 硬盘里 读到 内存中,排好序,再 写入到 硬盘中,这些段 叫做 归并段.最后将 这些 分段 合并 成 一个 最终  完整 有序的 数据. 这里 操作的 时间 =  内部 排序 时间 +  外存读写时间 + 内部归并所需时间. 其中 外存 读写时间 最耗时,外存读写时间 = 读写次数 * 读写数据的时间 ,读写 数据的时间 因 设备 性能 而 影响,我们 无法控制,我们 只能 控制