稀疏矩阵的表示与运算

  • 一.介绍
  • 二.实现稀疏矩阵的原理
    • 1.稀疏矩阵的顺序存储
    • 2.稀疏矩阵的转置T
    • 3.求转置的方法
    • 4.快速求转置法
    • 5.稀疏矩阵加法(减法同理)
    • 6.稀疏矩阵的乘法
  • 三.稀疏矩阵的代码定义
    • 1.稀疏矩阵的元素
    • 2.稀疏矩阵的定义
    • 3.稀疏矩阵的快速转置
    • 4.稀疏矩阵的加减法
    • 5.稀疏矩阵的乘法
  • 四.代码实现

一.介绍

什么是稀疏矩阵,假设我们有一个矩阵
(以下部分回去重新编辑)
[000000025000]\begin{bmatrix} 0 & 0 &0 &0\\ 0 &0 & 0 &2\\ 5 &0 & 0 & 0 \end{bmatrix} ⎣⎡​005​000​000​020​⎦⎤​

如果我们使用一个二维数组去存储它,需要开辟4x4的一个二维数组。但是数组里有大量的0,所以我们有没有什么办法去简化它的储存空间,肯定是有的。假设我们用一个一维数组来存储这个三元组{(1,3,2),(2,0,5)}然后并储存上它的尺寸,那么我们就可以用一个一维数组+两个int变量来存储这个矩阵。那么我们就称为这个矩阵为“稀疏矩阵”。

二.实现稀疏矩阵的原理

1.稀疏矩阵的顺序存储

当我们存储一个稀疏矩阵的时候,我们依然可以按行作为顺序或者按列作为顺序。
比如:
[020405700]\begin{bmatrix} 0& 2& 0\\ 4& 0& 5\\ 7& 0& 0 \end{bmatrix} ⎣⎡​047​200​050​⎦⎤​

这个矩阵,我们按行存储为{(0,1,2),(1,0,4),(1,2,5),(2,0,7)},如果按列储存则为{(1,0,4),(2,0,7),(0,1,2),(1,2,5)}

2.稀疏矩阵的转置T

假设我们有矩阵
[102004003007]\begin{bmatrix} 1& 0& 2& 0\\ 0& 4& 0& 0\\ 3& 0& 0& 7 \end{bmatrix} ⎣⎡​103​040​200​007​⎦⎤​

它的稀疏矩阵可以表示为
(0,0,1)
(0,2,2)
(1,1,4)
(2,0,3)
(2,3,7)
那么我们转置后为
[103040200007]\begin{bmatrix} 1& 0& 3\\ 0& 4& 0\\ 2& 0& 0\\ 0& 0& 7 \end{bmatrix} ⎣⎢⎢⎡​1020​0400​3007​⎦⎥⎥⎤​

其稀疏矩阵
(0,0,1)
(0,2,3)
(1,1,4)
(2,0,2)
(3,2,7)
我们不难发现,在稀疏矩阵的转置中,我们不仅矩阵的内容发生了改变(2,0,3)变成了(0,2,3)。而且在稀疏矩阵中的顺序也发生了改变,比如(2,0,3)从index=3变成了index=1。

3.求转置的方法

在二维数组中,我们求转置很简单,只需要循环赋值,交换一下角标A[m][n],它的转置T[n][m]即可。
在稀疏矩阵中,我们如果要求它的转置,需要交换一下每个元组里的前两个下标,并重新排下序即可(但是排序的时间复杂度高啊)。

4.快速求转置法

在稀疏矩阵中,我们可以观察发现,如果只按序排列的话,我们可以通过哈希表的方式,来通过一次遍历得到它转置后的位置。
先看刚才例子的两个矩阵:假设我们规定前两个下标为(i,j)

row col row col
0 0 1 0 0 1
0 2 2 0 2 3
1 1 4 1 1 4
2 0 3 2 0 2
2 3 7 3 2 7

不难发现:

  1. 两个的数组都是以i为升序排列的。
  2. 右侧的数组同一个i的下标的元组,顺序一定和左侧的数组顺序相同。

第一个性质很好理解。
第二个性质我们看看:那么左侧数组(0,0,1)在(2,0,3)上面,右侧数组在(0,0,1)也在(0,2,3)上面。
为啥?因为ij的过程中,因为i是依次递增的,所以到了j的时候,j也因该是线性变化的。
所以,即使右侧数组遇见了相同的i下标,那么j坐标是原来左侧的i,也是线性排布。
有了这两个性质,我们就可以用一个哈希表来存储它转置后的位置!
过程:

  1. 第一次循环左侧稀疏矩阵,得到以j下标作为键值的哈希表(就是统计个数)。比如刚才左侧的数组,我们的j下标的哈希表为 Hash[col]:Hash[0] = 2; Hash[1]=1; Hash[2]=1;Hash[3]=1;
  2. 用Hash表生成位置表,也就是我们每个元素开始的次序表:cpot[col]。其中,第0行,cpot[0]=0,然后cpot[col] = cpot[col-1]+Hash[col-1]。
  3. 再来一次循环,不过是从后往前循环,将ji下标进行交换,然后它的位置就是cpot[newi] + Hash[newi]-1。并--Hash[newi]。这样就利用Hash这个数组确定了它的位置。循环完成后,我们的转置就完成了。

5.稀疏矩阵加法(减法同理)

为了时间复杂度更小,我们通过开辟一个新的稀疏矩阵作为结果。并将大小开辟为相加的两个矩阵的和。并采用双指针开始赋值。前提是两个矩阵的形状要一样。

  1. 设置一个ptrA和ptrB,直到ptrA==A.len || ptrB==B.len
  2. 否则,就对比ptrA和ptrB指向的元素的值。
  3. 若相等,则值相加后加入到C并++ptrAB。若不等,判断下标的先后,然后加入到C并++ptrA或ptrB。
  4. 跳出循环后,看A和B数组有谁没有添加完,就全部添加到C。

6.稀疏矩阵的乘法

假设矩阵A为
[020105004]\begin{bmatrix} 0& 2& 0 \\ 1 &0& 5\\ 0 &0& 4 \end{bmatrix} ⎣⎡​010​200​054​⎦⎤​
(0,1,2)
(1,0,1)
(1,2,5)
(2,2,4)
矩阵B为
[600780]\begin{bmatrix} 6& 0\\ 0& 7\\ 8& 0 \end{bmatrix} ⎣⎡​608​070​⎦⎤​
(0,0,6)
(1,1,7)
(2,0,8)
那么我们的结果是
[014460320]\begin{bmatrix} 0 & 14 \\ 46 & 0\\ 32 & 0 \end{bmatrix} ⎣⎡​04632​1400​⎦⎤​

算法很简单,C(i,j)我们只需要将A中的所有元素(i,k)找到B中相等的元素(k,j)相乘相加即可。
举例:(0,1,2)就需要在B里找(1,j,?),那就是(1,1,7),那么得C(0,1,14)
(1,0,1)在B找(0,j,?),那就是(0,0,6),得C(1,0,6)
(1,2,5)在B找(2,j,?),那就是(2,0,8),得C(1,0,6+40)
(2,2,4)在B找(2,j,?),那就是(2,0,8),得C(2,0,32)
这样,我们的C就求完了

三.稀疏矩阵的代码定义

1.稀疏矩阵的元素

对于矩阵的一个元素,我们需要存在值value,行下标row,列下表col。那么就定义一个结构体,并开辟三个整数(为了方便,其实值也可以是一个结构体)。

2.稀疏矩阵的定义

一个矩阵,我们肯定要有矩阵的行大小和列大小,且稀疏矩阵因该还有变量存非零元素的个数。
那么我们还是动态生成一段空间储存元素,然后三个变量:mrow mcol nzero
其中我们规定:0 <= row < mrow 0 <= col < mcol

3.稀疏矩阵的快速转置

流程已经在上面原理介绍了,所以不赘述。

4.稀疏矩阵的加减法

流程也描述了,不赘述

5.稀疏矩阵的乘法

具体的算法代码可能会在后文写出。

四.代码实现

#include <stdio.h>
#include <stdlib.h>#define         OK          1
#define         ERROR       0typedef struct element{int row;int col;int value;
}element;typedef struct matrix{int mrow;int mcol;int nzero;element *base;
}matrix;        //定义元素和矩阵matrix *MatInit(void);
int MatShow(matrix *Mat, int way);
matrix *MatOP(matrix *MatA, matrix *MatB, int (*operation)(int a, int b));
int Plus(int a, int b);
int Subtract(int a, int b);
matrix* MatTrans(matrix *Mat);
matrix *MatMult(matrix *MatA, matrix *MatB);int main()
{matrix *mat1 = MatInit();//matrix *mat2 = MatInit();//matrix *mat3 = MatOP(mat1, mat2, Plus);//MatShow(mat3, 0);matrix *mat4 = MatTrans(mat1);MatShow(mat1, 0);MatShow(mat4, 0);return 0;
}matrix *MatInit(void)
{int i = 0;int row, col, value;matrix *Mat = (matrix*)malloc(sizeof(matrix));if (!Mat) exit(0); printf("Plase input the row(s) and col(s) of the Matrix and the number of data as int int int:\n"); scanf("%d %d %d", &row, &col, &value);Mat->mrow = row;Mat->mcol = col;Mat->nzero = value;if (Mat->mcol <= 0 || Mat->mrow <= 0 || Mat->nzero < 0 || Mat->nzero > (Mat->mrow*Mat->mcol)) exit(1);//输入并保存行信息Mat->base = (element*)malloc(sizeof(element)*Mat->nzero);if (Mat->nzero == 0){return Mat;}printf("\nplease input col row value:\n");for (; i < Mat->nzero; ++i){scanf("%d %d %d", &row, &col, &value);if (row < Mat->mrow && col < Mat->mcol){(Mat->base+i)->row = row;(Mat->base+i)->col = col;(Mat->base+i)->value = value;}else{printf("\ninput error\n");exit(1);}}return Mat;
}int MatShow(matrix *Mat, int way)
{int i = 0;int ptr = 0, row = 0, col = 0;if (way == 1)       //  这种输出方式就不多描述了{for (i = 0; i < Mat->nzero; ++i){printf("(%3d,%3d,%3d)\n", (Mat->base+i)->row, (Mat->base+i)->col, (Mat->base+i)->value);}return OK;}else{printf("Matrix:\n");while (row < Mat->mrow && col < Mat->mcol && ptr < Mat->nzero)// 循环的条件是 还有非零元素未输出或者未超出最大长度{if (row == (Mat->base+ptr)->row && col == (Mat->base+ptr)->col)     // 如果该位置有非零元素{printf("%3d ", (Mat->base+ptr)->value);++ptr;}else        // 零元素{printf("%3d ", 0);}++col;if (col == Mat->mcol)   // 如果输出完了一行,那么就让col从零开始 并到下一行{col = 0;++row;printf("\n");}}while(row < Mat->mrow && col < Mat->mcol)   // 有可能只是非零元素输出完了, 所以再把剩下的0补齐{printf("%3d ", 0);++col;if (col == Mat->mcol){col = 0;++row;printf("\n");}}}return OK;
}int Plus(int a, int b)
{return a+b;
}int Subtract(int a, int b)
{return a-b;
}matrix *MatOP(matrix *MatA, matrix *MatB, int (*operation)(int a, int b))   // 后面是个程序指针,用于传入是加还是减
{int ptrA = 0, ptrB = 0, ptrC = 0;int mark = 0;matrix *MatC = (matrix*)malloc(sizeof(matrix));if (MatA->mrow != MatB->mrow || MatA->mcol != MatB->mcol)   // 大小不同退出{printf("two mats have different size!");exit(1);}MatC->mrow = MatA->mrow;MatC->mcol = MatA->mcol;MatC->nzero = MatA->nzero + MatB->nzero;MatC->base = (element*)malloc(sizeof(element)*MatC->nzero);while(ptrA < MatA->nzero && ptrB < MatB->nzero)     // 还是遍历整个矩阵{mark = 0;       // 标记位, 如果是-1说明A有一个非零元素 在B前面if ((MatA->base+ptrA)->row < (MatB->base+ptrB)->row){mark = -1;}else if ((MatA->base+ptrA)->row == (MatB->base+ptrB)->row){if ((MatA->base+ptrA)->col < (MatB->base+ptrB)->col) // 标记位, 如果是-1说明A有一个非零元素 在B前面{mark = -1;}else if ((MatA->base+ptrA)->col == (MatB->base+ptrB)->col) // 标记位, 等于0,说明两个相同位置由非零元素{mark = 0;}else{mark = 1;}}else{mark = 1;   // 标记位, 等于1,说明B有个非零元素在A前面}if (mark == -1) // 把A非零元素操作了,并指向A的下一个元素{(MatC->base+ptrC)->col = (MatA->base+ptrA)->col;(MatC->base+ptrC)->row = (MatA->base+ptrA)->row;(MatC->base+ptrC)->value = operation(0,(MatA->base+ptrA)->value);++ptrA;++ptrC;}else if (mark == 0)// 把AB非零元素同时操作了,并指向AB的下一个元素{(MatC->base+ptrC)->col = (MatA->base+ptrA)->col;(MatC->base+ptrC)->row = (MatA->base+ptrA)->row;(MatC->base+ptrC)->value = operation((MatB->base+ptrB)->value,(MatA->base+ptrA)->value);++ptrA;++ptrB;++ptrC;}else     // 把B非零元素操作了,并指向B的下一个元素{(MatC->base+ptrC)->col = (MatB->base+ptrB)->col;(MatC->base+ptrC)->row = (MatB->base+ptrB)->row;(MatC->base+ptrC)->value = operation(0,(MatB->base+ptrB)->value);++ptrB;++ptrC;}}while (ptrA < MatA->nzero)  //  可能A有未输出的非零元素,将其全部输出{(MatC->base+ptrC)->col = (MatA->base+ptrA)->col;(MatC->base+ptrC)->row = (MatA->base+ptrA)->row;(MatC->base+ptrC)->value = operation(0,(MatA->base+ptrA)->value);++ptrA;++ptrC;}while (ptrB < MatB->nzero) //  可能B有未输出的非零元素,将其全部输出{(MatC->base+ptrC)->col = (MatB->base+ptrB)->col;(MatC->base+ptrC)->row = (MatB->base+ptrB)->row;(MatC->base+ptrC)->value = operation(0,(MatB->base+ptrB)->value);++ptrB;++ptrC;}return MatC;
}matrix* MatTrans(matrix *Mat)
{int i = 0, newi = 0, index;int *hash = (int*)malloc(sizeof(int)*Mat->mcol);int *cpot = (int*)malloc(sizeof(int)*Mat->mcol);matrix* MatNew = (matrix*)malloc(sizeof(matrix));MatNew->nzero = Mat->nzero;MatNew->mrow = Mat->mcol;MatNew->mcol = Mat->mrow;MatNew->base = (element*)malloc(sizeof(element)*(MatNew->nzero));for (i = 0; i < Mat->mcol; ++i)     //  初始化 hash和起始下标 数组{hash[i] = 0;cpot[i] = 0;}for (i = 0; i < Mat->nzero; ++i)    // 统计hash{hash[(Mat->base+i)->col] += 1;}for (i = 1; i < Mat->mcol; ++i) //  统计cpot{cpot[i] = cpot[i-1] + hash[i-1];}for (i = Mat->nzero - 1; i >= 0; --i)   //倒叙输出,将通过hash和cpot同时确定元素位置。{newi = (Mat->base+i)->col;index = cpot[newi]+hash[newi]-1;(MatNew->base+index)->row = (Mat->base+i)->col;(MatNew->base+index)->col = (Mat->base+i)->row;(MatNew->base+index)->value = (Mat->base+i)->value;--hash[newi];}return MatNew;
}

【数据结构学习记录13】——稀疏矩阵的表示与运算相关推荐

  1. springboot @cacheable不起作用_Springboot学习记录13 使用缓存:整合redis

    本学习记录的代码,部分参考自gitee码云的如下工程.这个工程有详尽的Spingboot1.x教程.鸣谢! https://gitee.com/didispace/SpringBoot-Learnin ...

  2. 数据结构学习记录——哈夫曼树(什么是哈夫曼树、哈夫曼树的定义、哈夫曼树的构造、哈夫曼树的特点、哈夫曼编码)

    目录 什么是哈夫曼树 哈夫曼树的定义 哈夫曼树的构造 图解操作 代码实现 代码解析 哈夫曼树的特点 哈夫曼编码 不等长编码 二叉树用于编码 哈夫曼编码实例 什么是哈夫曼树 我们先举个例子: 要将百分制 ...

  3. 数据结构学习记录---天勤线性表综合应用题(2)

    记录考研数据结构学习过程中的代码实现 (参考天勤书本) (天勤P40T1(6)法一)删除链表中重复元素(常规方法发现与前相同则删除) (天勤P40T1(6)法二)删除链表中重复元素(全部不重复元素移动 ...

  4. 【数据结构学习记录22】——有向无环图及其应用

    有向无环图及其应用 一.有向无环图的概念 二.拓扑排序(AOV网) 1.概念 2.偏序与全序 a).偏序 b).全序 c).偏序与全序的区别 3.拓扑有序 4.拓扑排序的过程 三.关键路径(AOE网) ...

  5. 量子信息-学习记录13

    ch.13. 量子计算机的物理实现(续) 量子计算机的基本事实   定义:τQ\tau_QτQ​是量子系统在抵抗量子噪声,并维持自身的量子特性时所能够持续的最短时间 τQ=min⁡{T1,T2}\ta ...

  6. 数据结构学习记录(二)——折半查找二叉判定树的画法

    以下给出我在学习中总结的一种比较简便的构造折半二叉判定树的思路以及方法: 思路分析: 在计算mid值时,使用的时mid=(low+high)/2  .这里由于mid为int类型,自动默认为向下取整,因 ...

  7. 编程学习记录13:Oracle数据库,表的查询

    表达查询是数据库中最常用的操作,最基本的语句为 SELECT <列名.值.函数> FROM <表名> 当在 SELECT 后使用 * 即表示所查询表中的所有列,如SELECT ...

  8. 数据结构学习记录连载1

    1.题目:基本要求:1) 建立线性表的顺序表类SeqList, 提高要求:在顺序表类SeqList增加一个删除函数,要求删除顺序表中等于item的所有元素. 1.SeqList.h: /* * Cop ...

  9. 网络安全学习记录-13

    XXE-实体注入 XML外部实体注入攻击:用户输入的数据当作XML外部实体代码进行执行 XML的宗旨传输数据与html相像 没有预定义 eg:<fenshu>xiaoming</fe ...

最新文章

  1. php acl控制,用PHP怎么实现一个ACL系统?
  2. linux进程网络均衡,linux多CPU进程负载均衡解析
  3. php中自动转换、强制转换、其他数据类型和bool转换
  4. freemarker使用说明_SpringBoot+Swagger2集成详细说明
  5. 史上最新最全的来自成都的Azure系列文章,助你上云!老少皆宜,童叟无欺!
  6. redis相对于mysql有什么劣势
  7. html 页面怎么自动定位到某个标签,JS如何实现在页面上快速定位(锚点跳转问题)...
  8. linux 单步启动_Linux内核如何装载和启动一个可执行程序
  9. 图像检索:FCTH(Fuzzy Color and Texture Histogram)算法
  10. 圣诞节的整理前两周的内容4
  11. Hibernate SQL 方言(hibernate.dialect)
  12. Python和RF编写接口自动化
  13. django——三种方式实现文件下载
  14. PMP-资源优化:资源平衡、资源平滑区别和举例,附对比图收藏
  15. ios 描述文件位置
  16. npm安装任何包都报错解决方法
  17. Python 代码实现ArcGis 标注Label转注记Annotation
  18. Latex参考文献问题---参考文献条数不显示
  19. 程序员的工资这么高,为什么还会有人离职?
  20. dos命令行-禁用和启用本地连接

热门文章

  1. linux使用链接下载文件
  2. buu re 新年快乐
  3. 基于SSM的零食商城管理系统
  4. java将excel文件转换成txt格式文件
  5. 免费的证件照在线制作工具
  6. 北京工业计算机英语口语复试,2018考研复试英语口语常见面试问题
  7. 主R 主A 什么意思
  8. 中国历史可以用几句话总结
  9. Linux:python捕获异常,模板,文件以及数据存储
  10. 微服务链路追踪SkyWalking第十一课 OAL详解实战