【数据结构学习记录13】——稀疏矩阵的表示与运算
稀疏矩阵的表示与运算一.介绍二.实现稀疏矩阵的原理1.稀疏矩阵的顺序存储2.稀疏矩阵的转置T3.求转置的方法4.快速求转置法5.稀疏矩阵加法(减法同理)6.稀疏矩阵的乘法三.稀疏矩阵的代码定义1.稀疏矩阵的元素2.稀疏矩阵的定义3.稀疏矩阵的快速转置4.稀疏矩阵的加减法5.稀疏矩阵的乘法四.代码实现一.介绍什么是稀疏矩阵,假设我们有一个矩阵(以下部分回去重新编辑)[000000025000]\be
稀疏矩阵的表示与运算
一.介绍
什么是稀疏矩阵,假设我们有一个矩阵
(以下部分回去重新编辑)
[
0
0
0
0
0
0
0
2
5
0
0
0
]
\begin{bmatrix} 0 & 0 &0 &0\\ 0 &0 & 0 &2\\ 5 &0 & 0 & 0 \end{bmatrix}
⎣⎡005000000020⎦⎤
如果我们使用一个二维数组去存储它,需要开辟4x4的一个二维数组。但是数组里有大量的0,所以我们有没有什么办法去简化它的储存空间,肯定是有的。假设我们用一个一维数组来存储这个三元组{(1,3,2),(2,0,5)}然后并储存上它的尺寸,那么我们就可以用一个一维数组+两个int变量来存储这个矩阵。那么我们就称为这个矩阵为“稀疏矩阵”。
二.实现稀疏矩阵的原理
1.稀疏矩阵的顺序存储
当我们存储一个稀疏矩阵的时候,我们依然可以按行作为顺序或者按列作为顺序。
比如:
[
0
2
0
4
0
5
7
0
0
]
\begin{bmatrix} 0& 2& 0\\ 4& 0& 5\\ 7& 0& 0 \end{bmatrix}
⎣⎡047200050⎦⎤
这个矩阵,我们按行存储为{(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
假设我们有矩阵
[
1
0
2
0
0
4
0
0
3
0
0
7
]
\begin{bmatrix} 1& 0& 2& 0\\ 0& 4& 0& 0\\ 3& 0& 0& 7 \end{bmatrix}
⎣⎡103040200007⎦⎤
它的稀疏矩阵可以表示为
(0,0,1)
(0,2,2)
(1,1,4)
(2,0,3)
(2,3,7)
那么我们转置后为
[
1
0
3
0
4
0
2
0
0
0
0
7
]
\begin{bmatrix} 1& 0& 3\\ 0& 4& 0\\ 2& 0& 0\\ 0& 0& 7 \end{bmatrix}
⎣⎢⎢⎡102004003007⎦⎥⎥⎤
其稀疏矩阵
(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 |
不难发现:
- 两个的数组都是以
i
为升序排列的。 - 右侧的数组同一个
i
的下标的元组,顺序一定和左侧的数组顺序相同。
第一个性质很好理解。
第二个性质我们看看:那么左侧数组(0,0,1)在(2,0,3)上面,右侧数组在(0,0,1)也在(0,2,3)上面。
为啥?因为i
变j
的过程中,因为i
是依次递增的,所以到了j
的时候,j
也因该是线性变化的。
所以,即使右侧数组遇见了相同的i
下标,那么j
坐标是原来左侧的i
,也是线性排布。
有了这两个性质,我们就可以用一个哈希表来存储它转置后的位置!
过程:
- 第一次循环左侧稀疏矩阵,得到以
j
下标作为键值的哈希表(就是统计个数)。比如刚才左侧的数组,我们的j
下标的哈希表为 Hash[col]:Hash[0] = 2; Hash[1]=1; Hash[2]=1;Hash[3]=1; - 用Hash表生成位置表,也就是我们每个元素开始的次序表:cpot[col]。其中,第0行,cpot[0]=0,然后cpot[col] = cpot[col-1]+Hash[col-1]。
- 再来一次循环,不过是从后往前循环,将
j
和i
下标进行交换,然后它的位置就是cpot[newi] + Hash[newi]-1。并--Hash[newi]
。这样就利用Hash这个数组确定了它的位置。循环完成后,我们的转置就完成了。
5.稀疏矩阵加法(减法同理)
为了时间复杂度更小,我们通过开辟一个新的稀疏矩阵作为结果。并将大小开辟为相加的两个矩阵的和。并采用双指针开始赋值。前提是两个矩阵的形状要一样。
- 设置一个ptrA和ptrB,直到
ptrA==A.len || ptrB==B.len
。 - 否则,就对比ptrA和ptrB指向的元素的值。
- 若相等,则值相加后加入到C并++ptrAB。若不等,判断下标的先后,然后加入到C并++ptrA或ptrB。
- 跳出循环后,看A和B数组有谁没有添加完,就全部添加到C。
6.稀疏矩阵的乘法
假设矩阵A为
[
0
2
0
1
0
5
0
0
4
]
\begin{bmatrix} 0& 2& 0 \\ 1 &0& 5\\ 0 &0& 4 \end{bmatrix}
⎣⎡010200054⎦⎤
(0,1,2)
(1,0,1)
(1,2,5)
(2,2,4)
矩阵B为
[
6
0
0
7
8
0
]
\begin{bmatrix} 6& 0\\ 0& 7\\ 8& 0 \end{bmatrix}
⎣⎡608070⎦⎤
(0,0,6)
(1,1,7)
(2,0,8)
那么我们的结果是
[
0
14
46
0
32
0
]
\begin{bmatrix} 0 & 14 \\ 46 & 0\\ 32 & 0 \end{bmatrix}
⎣⎡046321400⎦⎤
算法很简单,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 0
typedef 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;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)