数据结构与算法——第5章-数组和广义表-行逻辑链接的顺序表实现矩阵乘法(5.9.2)

一 概述

1
2
3
1.过程描述
2.攻克问题难点
3.具体实现代码

二 过程描述

1
2
3
4
5
6
7
8
9
对矩阵的乘积进行深度剖析,矩阵 A 和矩阵 B 相乘的运算过程是这样的:
1. 首先,找到矩阵 A 中第一行的非 0 元素,分别是 A 11 = 3 和 A 14 = 5;
(由于行逻辑链接的顺序表中存储的都是非 0 元素,查找的过程就需要使用记录每行第一个非 0 元素的首地址的数组来完成)

2. 用 3 去和 B 中对应的第一行中的非 0 元素相乘,矩阵 B 中第一行非 0 元素是 B 12 = 2,所以 3*2 = 6 ,
因为 6 是 A 11 和 B 12 相乘的结果,所以暂时存放在 C 12 中;
用 5 去和 B 中对应的第 4 行的非 0 元素相乘,由于矩阵 B 中第 4 行没有非 0 元素,所以,第一行的计算结束;

3. 以此类推。

三 攻克问题难点

1
2
3
4
5
6
7
现在,解决问题的关键在于,如何知道顺序表中存放的非 0 元素是哪一行的呢?

解决方案:由于使用的是行逻辑链接的顺序表,
所以,已经知道了每一个矩阵中的每一行有多少个非 0 元素,而且第一行的第一个非 0 元素的位置一定是 1。

所以,第 n 行的非 0 元素的位置范围是:
大于或等于第 n 行第一个元素的位置, 小于第 n+1 行第一个元素的位置(如果是矩阵的最后一行, 小于矩阵中非 0 元素的个数 + 1)。

四 具体实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <stdio.h>
#define MAXSIZE 12500
#define MAXRC 100
#define ElemType int
typedef struct {
int i,j;//行,列
ElemType e;//元素值
} Triple;
typedef struct {
Triple data[MAXSIZE+1];
int rpos[MAXRC+1];//每行第一个非零元素在 data 数组中的位置
int mu,nu,tu;//行数,列数,元素个数
} RLSMatrix;
RLSMatrix MultSMatrix(RLSMatrix A, RLSMatrix B, RLSMatrix C) {
//如果矩阵 A 的列数与矩阵 B 的行数不等,则不能做矩阵乘运算
if(A.nu != B.mu)
return C;
C.mu = A.mu;
C.nu = B.nu;
C.tu = 0;
//如果其中任意矩阵的元素个数为零,做乘法元素没有意义,全是 0
if(A.tu * B.tu == 0)
return C;
else {
int arow;
int ccol;
//遍历矩阵 A 的每一行
for(arow=1; arow<=A.mu; arow++) {
//创建一个临时存储乘积结果的数组,且初始化为 0,遍历每次都需要清空
int ctemp[MAXRC+1] = {};
C.rpos[arow] = C.tu + 1;
//根据行数,在三元组表中找到该行所有的非 0 元素的位置
int tp;
if(arow < A.mu)
tp = A.rpos[arow+1];//获取矩阵 A 的下一行第一个非零元素在 data 数组中位置
else
tp = A.tu+1;//若当前行是最后一行,则取最后一个元素+1
int p;
int brow;
//遍历当前行的所有的非 0 元素
for(p=A.rpos[arow]; p<tp; p++) {
brow = A.data[p].j;//取该非 0 元素的列数,便于去 B 中找对应的做乘积的非 0 元素
int t;
// 判断如果对于 A 中非 0 元素,找到矩阵 B 中做乘法的那一行中的所有的非 0 元素
if(brow < B.mu)
t = B.rpos[brow+1];
else
t = B.tu+1;
int q;
//遍历找到的对应的非 0 元素,开始做乘积运算
for(q=B.rpos[brow]; q<t; q++) {
//得到的乘积结果,每次和 ctemp 数组中相应位置的数值做加和运算
ccol = B.data[q].j;
ctemp[ccol] += A.data[p].e * B.data[q].e;
}
}
//矩阵 C 的行数等于矩阵 A 的行数,列数等于矩阵 B 的列数,所以,得到的 ctemp 存储的结果,也会在 C 的列数的范围内
for(ccol=1; ccol<=C.nu; ccol++) {
//由于结果可以是 0,而 0 不需要存储,所以在这里需要判断
if(ctemp[ccol]) {
//不为 0,则记录矩阵中非 0 元素的个数的变量 tu 要+1;且该值不能超过存放三元素数组的空间大小
if(++C.tu > MAXSIZE)
return C;
else {
C.data[C.tu].e = ctemp[ccol];
C.data[C.tu].i = arow;
C.data[C.tu].j = ccol;
}
}
}
}
return C;
}
}
int main(int argc, char* argv[]) {
RLSMatrix M,N,T;
M.tu = 4;
M.mu = 3;
M.nu = 4;
M.rpos[1] = 1;
M.rpos[2] = 3;
M.rpos[3] = 4;
M.data[1].e = 3;
M.data[1].i = 1;
M.data[1].j = 1;
M.data[2].e = 5;
M.data[2].i = 1;
M.data[2].j = 4;
M.data[3].e = -1;
M.data[3].i = 2;
M.data[3].j = 2;
M.data[4].e = 2;
M.data[4].i = 3;
M.data[4].j = 1;
N.tu = 4;
N.mu = 4;
N.nu = 2;
N.rpos[1] = 1;
N.rpos[2] = 2;
N.rpos[3] = 3;
N.rpos[4] = 5;
N.data[1].e = 2;
N.data[1].i = 1;
N.data[1].j = 2;
N.data[2].e = 1;
N.data[2].i = 2;
N.data[2].j = 1;
N.data[3].e = -2;
N.data[3].i = 3;
N.data[3].j = 1;
N.data[4].e = 4;
N.data[4].i = 3;
N.data[4].j = 2;
T= MultSMatrix(M,N,T);
for (int i=1; i<=T.tu; i++) {
printf("(%d,%d,%d)\n",T.data[i].i,T.data[i].j,T.data[i].e);
}
return 0;
}

输出结果:

1
2
3
(1,2,6)
(2,1,-1)
(3,2,4)

五 总结

1
2
当稀疏矩阵 A mn 和稀疏矩阵 B np 采用行逻辑链接的顺序表做乘法运算时,在矩阵 A 的列数(矩阵 B 的行数) n 不是很大的情况下,
算法的时间复杂度相当于 O(m*p),比普通算法要快很多。

六 参考

  • C语言中文网—行逻辑链接的顺序表实现矩阵乘法