数据结构第五章:数组与广义表
- 稀疏矩阵指非零元占矩阵元素总数比例小于5%的一类矩阵,可以用三元组表表示,用某种方法来组织稀疏矩阵的三元组表的存储就可以实现矩阵的压缩存储
三元组顺序表
- 用顺序表表示稀疏矩阵三元组,如下:
struct Triple{
int i,j;//行索引,列索引
ElemType e;
}
struct TSMatrix{
Triple data[MAXSIZE + 1];
int mu, nu, tu;//行数,列数,非零元个数
}
- 需要约定三元组在顺序表中的排列顺序,如以行序为主序,以列序为副序进行排序
- 以上述方式组织的稀疏矩阵,转置时需要:
- 交换 mu 和 nu
- 交换每个三元组的 i 和 j
- 重新进行排序,重新进行排序的方法有两种:
- 一种是暴力遍历,遍历每个元素,找到 i j 交换前列序为1的元素按顺序放进新三元组,然后是列序为2的,以此类推。但这样运算复杂度太大了,太冗杂了,有很多无用的遍历;
- 第二种方法:转置前,先遍历一遍顺序表,记录下每列的非零元个数 num[col](通过累加法),然后对num[col]进行累加就能得到每一列第一个非零元在转置后的顺序表中的位序cpot[col]。根据cpot[col]对转置前的顺序表进行遍历,就可以直接得知每个元素在转置后的顺序表中的位置,直接放入。代码如下:
void FastTransposeSMatrix(TSMatrix &M, TSMatrix &T)
{
T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
if(T.tu)
{
//获取原矩阵每列元素个数
int num[MAXCOL+1]={0};
// 注意!!! 为了直观解释算法,索引都是从1开始,如果要拿代码去跑自己从0开始索引的数组,记得改一下
for(int i=1; i<=T.tu; i++)
{
int col = M.data[i].j;
num[col]++;
}
//获取新矩阵每行首元素在顺序表中的位置
int cpot[MAXCOL+1]={0};
cpot[1]=1;
for(int col=2; col<=M.mu; col++)
{
cpot[col] = num[col-1] + cpot[col - 1];
}
//按顺序将原矩阵各个非零原填入新矩阵顺序表中
for(int i=1; i<=M.tu; i++)
{
int new_i = cpot[M.data[i].j];
cpot[M.data[i].j]++;
T.data[new_i].e = M.data[i].e;
T.data[new_i].i = M.data[i].j;
T.data[new_i].j = M.data[i].i;
}
}
return;
}
行逻辑链接的顺序表
- 如果把上述得到的 cpot 数组作为TSMatrix T的成员,就形成了“行逻辑链接的顺序表”,优点是方便根据行号进行索引。
- 行逻辑链接的顺序表相比三元组顺序表,更方便进行稀疏矩阵乘法等操作。稀疏矩阵乘法的原理为:设 Q = M × N Q=M\times N Q=M×N,则M的第 a 列元素一定是与N的第 a 行元素相乘的,M的第 i 行 第 a 列元素与N的第 j 列 第 a 行元素的乘积将作为Q矩阵第 i 行第 j 列的一个累加项。
- 根据行逻辑链接的顺序表以行为主序的特点,可以一行一行地对Q进行求解,首先求Q的第一行,然后放进Q的稀疏矩阵行逻辑链接顺序表中,然后求第二行,以此类推。Q的第 i 行只需要考虑 M 的第 i 行 ,首先初始化一个数组 Q i = { Q i 1 , Q i 2 , . . . , Q i j , . . . } Q_{i} = \{Q_{i1},Q_{i2},...,Q_{ij},... \} Qi={Qi1,Qi2,...,Qij,...}为全0用于等待累加,当遍历至 M 的第 i 行的某个元素 M i a M_{ia} Mia时,根据其列序号 a,在N中寻找对应行序号为 a 的所有元素 N a j N_{aj} Naj,求乘积 M i a × N a j M_{ia}\times N_{aj} Mia×Naj,累加至 Q i j Q_{ij} Qij中。当遍历完M的第 i 行的所有元素后,Q的第 i 行计算完成,将 Q i Q_i Qi中的非零元顺序插入到Q矩阵的行逻辑链接顺序表的末尾。遍历完M的所有非全零行,则可得到完整的Q矩阵行逻辑链接顺序表。
- 直接上代码(仅作参考,没有debug过)
void MatMul(TSMatrix &M, TSMatrix &N, TSMatrix &Q)
{
Q.mu = M.mu;
Q.nu = N.nu;
for(int i=1; i<=Q.mu; i++)
Q.cpot[i] = 0;
int i_Q = 1;
for(int row_Q=1; row_Q<=M.mu; row_Q++)
{
// init array of current row in Q
float line_Q[MAXCOL+1];
for(int i=1; i<=Q.nu; i++)
line_Q[i] = 0.0;
// iter through all element in row_Q in M
int start_of_row_Q_in_M = M.cpot[row_Q];
if(start_of_row_Q_in_M == 0)// row_Q in M is all zero
continue;
for(int i = start_of_row_Q_in_M; i<=M.tu; i++)// else
{
if(M.data[i].i != row_Q)// end of row_Q
break;
int row_N = M.data[i].j;
//iter through all element in row_N in N
int start_of_row_N_in_N = N.cpot[row_N];
if(start_of_row_N_in_N == 0)// row_N in M is all zero
continue;
for(int j = start_of_row_N_in_N ; j<=N.tu; j++)// else
{
if(N.data[j].i != row_N)// end of row_N
break;
line_Q[N.data[j].j] += M.data[i].e * N.data[j].e;
}
}
// get cpot in Q
int first_not_zero = 0;
for(int i=1; i<=Q.nu; i++)
if(line_Q[i] != 0)
{
Q.cpot[row_Q] = i_Q;
first_not_zero = i;
break;
}
// add element to Q
if(first_not_zero != 0)
for(int i=first_not_zero ; i<=Q.nu; i++)
{
if(line_Q[i] != 0)
{
Q.data[i_Q].e = line_Q[i];
Q.data[i_Q].i = row_Q;
Q.data[i_Q].j = i;
i_Q++;
}
}
}
Q.tu = i_Q - 1;
}
链式存储结构的三元组——十字链表
- 每个非零元用链表的一个结点表示,结点含5个变量:行 i、列 j、值 e、行上下一个结点的指针 right,列上下一个结点的指针 left。
- 链表结构体包含两个数组,一个是行头指针数组,一个是列头指针数组。
广义表就不计了,感觉没什么用