三元组压缩
在之前的博客《数组的压缩存储》中提到过,对于一个稀疏矩阵我们可以采用三元组的方式来进行压缩,即存储行、列、数值信息这三元来代替一个二维数组。
之前博客的截图:
转置
将一个二维数组的每一个数据的行列信息对调得到的新矩阵就是原矩阵的转置。
如矩阵:
0 0 0 1
1 0 0 2
0 0 1 2
变成:
0 1 0
0 0 0
0 0 1
1 2 2
的过程就是求转置的过程。
- 转置矩阵的行列数和之前的列行数相同,则有可能后来的数组格式和之前不同,所以需要采用新的数组或者采用压缩存储方式来存储新的数组。
- 另外如果简单的把三元组中的行列数据调换,那么这个博客就失去了意义,因为原来的数据为有序的,所以我们在调换之后也要保证有序
(所谓的有序是指按行的顺序存储每一个结点)
方法一:循环查找
struct three
{
int col,row;
int data;
}
struct TSMatrix
{
struct three data[MAXNUM+1];
//maxnum是自己define的一个数值,表示这个数组最大有多少个非零元 ,+1则将第一个位置空出来
int c_num, r_num;//二维数组列数、行数
int number; //二维数组的非零元个数
}
struct TSMatrix Transpose(struct TSMatrix M)
{
struct TSMatrix T;
T.c_num = M.r_num;
T.r_num = M.c_num;
T.number=M.number;//初始化
if(T.number)
{
int p=1, q=1;
for(int col = 1; col <= M.c_num; col++)//按行来寻找
for(; p <= M.number; p++)//对每一个三元组
if(M.data[p].col == col)
{
T.data[q].row = M.data[p].col;
T.data[q].col = M.data[p].row;
T.data[q].data = M.data[p].data;
q++;
}
}
return T;
}
说白了因为转置后按行存储就是将原来的数组按列存储,所以按照列的顺序找到每一个三元组放在新的结构体里面就行了。
方法二:
总感觉这样的方法有一点不合适,毕竟是一个o(c_num * number)大小时间复杂度。
而我们要是选择二维数组的方式,转置一个数组也就是o(行数 * 列数),搞不好还不如之前的算法
此时我们需要用两个数组来改进,一个是num[col]数组,存储每一列有多少个元素,另一个是cpot[col]数组,cpot数组的第一位是1,剩下的cpot[i] = cpot[i-1]+num[i-1];
先看一下这个数组的num和cpot数组:(就是上面的案例)
0 0 0 1
1 0 0 2
0 0 1 2
这时我们可以看出来,cpot数组的每一项就是原数组按列排序后每一列第一个元素所在的位置。
所以,操作来了,只要每一次取一个三元组按spot数组找到位置放在新结构体中然后将对应的cpot项加一即可。(如果是像上面的第二列那样没有非零元也没关系,直接取不到就是了)
struct TSMatrix Transpose(struct TSMatrix M)
{
struct TSMatrix T;
T.c_num = M.r_num;
T.r_num = M.c_num;
T.number=M.number;//初始化
int num[c_num+1] = {0};//两个数组(第一位都不存储)
int cpot[c_num+1];
for(int i=1;col <= M.number; col++)
num[M.data[i].col]++;//数num每一项的个数,看着有点绕
cpot[1] = 0;
for(int i = 2; i<= M.number; i++)
cpot[i] = cpot[i-1] + num[i-1];
for(int p=1; p <= M.number; p++)
{
int col = M.data[p].col;//确定列
int q = cpot[col];//确定位置
T.data[q].row = col;
T.data[q].col = M.data[p].row;
T.data[q].data = M.data[p].data;
cpot[col]++;
}
虽然在方法二中我们使用了两个向量,但是我们成功将时间复杂度降到了线性阶。
其实如果能在spot数组上稍作改动,num数组也可以不需要。
加减运算
利用三元组存储的二维数组实现加减运算也为按行存储。
加法:
加减法都是需要两个矩阵的行列数相同!
创建一个新的结构体,其中三元组的大小应当为两个三元组之和,元素个数为第一个数组的个数,(之后不断向上加)
按照数组顺序遍历两个三元组,第一位看row大小,第二位是看col大小,小的在前面,相同就将两个data相加即可。
最后别忘了处理一下总节点个数(第二个三元组有不一样的就加1),还有结构体里面的行数列数。
减法么,就是把第二个三元组data项取反求和就可了。
矩阵相乘
先复习一下用二维数组算矩阵乘积的方法:
新矩阵的行数为第一个矩阵的行数,列数为第二个矩阵的列数,并且要求第一个矩阵的列数和第二个矩阵的行数相同。
其实本质上就是这个式子:
这里面因为是稀疏矩阵,所以有很多项都是为0的,求和不考虑,所以我们只看M中j和N中i相同的。
对于结构体,我们引进了一个rpot[row+1]数组,存储每一行第一个元素在data数组中的位置;
另外我们因为是对M/按行处理,需要一个和N的列数相同的累加器数组,temp[N.c_num+1],因为虽然可能是有乘积的存在,但是在求和过程中不一定能做到该元素一定非零。因为是计数器所以初始化为0。
struct RLSMatrix
{
struct three data[MAXNUM+1];
int rpos[MAXROW+1];//MAXROW是我们define的一个量,表示最大行数
int r_number, c_number;
int number;
}
struct RLSMatrix MultSMatrix(struct RLSMatrix M, struct RLSMatrix N)
{
if(M.c_num != N.r_num)
return NULL;
struct RLSMatrix Q;//Q的创建和初始化
Q.r_num = M.r_num;
Q.c_num = N.c_num;
Q.number = 0;//没什么办法确定有多少,所以选择了初始化为0
if(M.number * N.number)
{
int temp[N.c_num+1];//计数器
for(int row=1; row<=M.r_num; row++)//对M一行行来
{
for(int i=1; i<=N.c_num; i++)//每一次计数器都记得清零!
temp[i] = 0;
Q.rpos[row] = Q.number+1;//确定Q的rpos数组,
//因为之前个数都加在总数上了,再加一就是下一行的起始
int tp;//下一行的第一个元素,也就是这一行的临界
if(row < M.r_num)
tp = M.rpos[row+1];
else
tp = M.number+1;
for(int p=N.rpos[row]; p<tp; p++)//找到M中row行的内容
{
int roww = M.data[p].col;//选择其中一个三元组,找到对应的列,也就是N的行
int t;//和上面一样,N的行的临界条件
if(roww < N.r_num)
t = N.rpos[roww+1];
else
t = N.tu+1;
for(int q=N.rops[roww]; q<t; q++)//在不越界的情况下对每一个符合的求积并相加
temp[N.data[q].col] += M.data[p].data*N.data[q].data;
}
for(int col=1; col<=Q.c_num; col++)//将计数器的内容放到最后的三元组中,完成
if(temp[col])
{
if(++Q.tu > MAXSIZE)//数组超过了最大数,溢出
exit(0);
Q.data[Q.tu].row = row;
Q.data[Q.tu].col = col;
Q.data[Q.tu].data = temp[col];
}
}
}
return Q;
}