稀疏矩阵的十字链表实现
当矩阵中的非零元的个数和位置在操作过程中变化较大时,就不宜采用顺序存储结构来表示三元组的线性表。例如在进行矩阵乘法时,需要用第一个矩阵的每一行与第二个矩阵的每一列进行相乘。在之前《
顺序存储结构的三元组稀疏矩阵的乘法运算》
中我们也可以看出,每计算出一个结果都要将其与结果矩阵中现有的三元组进行行列值的比较,若行列值都相等,则将后者的元素值加到前者元素值之上,再将计算结果的空间释放,若行列值有一个不相等,则将结果保存。每计算出一个结果都要与之前的所有结果进行比较,越往后需要比较的越多,增加了算法的复杂度。并且为了在第二个矩阵中找到所有行值与第一个矩阵项的列值相等的项,对第一个矩阵的每个项的运算都需要遍历第二个矩阵的所有项,使得算法的复杂度为两个矩阵的非零元个数的乘积。而使用十字链表存储结构的稀疏矩阵的乘法运算的复杂度诶为两个矩阵的非零元个数之和。
完整代码可到CSDN的资源中搜索《稀疏矩阵的十字链表表示方法:矩阵加减乘法运算、矩阵转置运算、矩阵项的插入、矩阵行列链表的排序》进行下载。
十字链表的每个非零元可以用一个含有5个域的结点表示,其中i,j和value分别表示该非零元所在的行号、列号和该非零元的值,向右域right泳衣链接同一行中下一个非零元,向下域用于链接同一列中的写一个非零元。同一行的非零元通过right域链接成一个线性链表,同一列的非零元通过down域链接成一个线性链表。每个非零元既是某个行链表的节点,也是某个列链表的结点,整个矩阵构成了一个十字交叉链表,因此称这样的存储结构为十字链表。我们可以用两个分别存储行链表的头指针和列链表的头指针的一维数组表示它们。
<span style="font-size:12px;">typedef int ElemType;
typedef struct OLNode{
int row; //此处的行列值为真实值,都有从1开始没有第0行和第0列
int col;
ElemType value; //矩阵第row行第col列对应的值
struct OLNode *right;
struct OLNode *down;
}OLNode;
typedef struct{
OLNode *rhead; //行链表头指针数组的基地址
OLNode *chead;//列链表头指针数组的基地址
int row_num; //矩阵的总行数
int col_num; //矩阵的总列数
int non_zero; //矩阵总的非零值的个数
}CrossList;
</span>
1、由于在运算过程中要多次涉及到矩阵的初始化,因此我们将其写成一个函数方便使用。该函数的功能是:为十字链表型的稀疏矩阵分配行列链表的头指针数组的空间,并将其进行初始化。
int Malloclist( CrossList *matrix )
{
int i;
matrix->rhead = ( OLNode *)malloc( sizeof( OLNode ) * ( matrix->row_num + 1 ) ); //为矩阵的行列链表的头指针分配空间
matrix->chead = ( OLNode *)malloc( sizeof( OLNode ) * ( matrix->col_num +1 ) );
if( !matrix->rhead || !matrix->chead ){ //分配存储空间失败,报错并结束程序执行
return FALSE;
}
for( i = 0; i <= matrix->row_num; i++ ){//对行链表的头进行初始化
( matrix->rhead + i )->right = NULL;
( matrix->rhead + i )->down = NULL;
}
for( i = 0; i <= matrix->col_num; i++ ){//对列链表的头进行初始化
( matrix->chead + i )->right = NULL;
( matrix->chead + i )->down = NULL;
}
return TRUE;
}
2、使用十字链表的表示方法比较麻烦的地方就是每计算出一个矩阵项,都要将其插入到对应的行列链表中的合适位置。在创建矩阵、复制矩阵、矩阵的加减法等都需要进行插入操作。但其原理都是相同的,就是对单链表的一个排序。只不过行链表排序时需要按照行号进行排序,且使用矩阵项的right指针,而列链表排序时需要按照列号进行排序,且使用矩阵项的down指针。我们可以将行列链表的插入和排序分开写成不同的函数。
<span style="font-size: 14px;">//对行链表按列递增的顺序进行排序
</span><span style="font-size:12px;">void Increse_row( OLNode *List_RHead )
{
OLNode *current;
OLNode *nex;
OLNode *pre;
int count = 0;
int i,j;
current = List_RHead->right;
while( current ){ //统计当前列表的项数
count++;
current = current->right ;
}
for( i = 1; i < count; i++ ){ //冒泡法进行排序
current = List_RHead->right;
pre = List_RHead->right;
for( j = 0; j < count - i; j++ ){
nex = current->right;
if( current->col > nex->col ){
current->right = nex->right;
nex->right= current;
if( current == List_RHead->right ){
List_RHead->right = nex;
pre = List_RHead->right;
}
else{
pre->right = nex;
pre = pre->right;
}
}
else{
pre = current;
current = nex;
}
}
}
}
//将列链表按照行递增的顺序进行排序
void Increse_col( OLNode *List_CHead )
{
OLNode *current;
OLNode *nex;
OLNode *pre;
int count = 0;
int i,j;
current = List_CHead->down;
while( current ){ //统计当前列表的项数
count++;
current = current->down ;
}
for( i = 1; i < count; i++ ){//冒泡法进行排序
current = List_CHead->down;
pre = List_CHead->down;
for( j = 0; j < count - i; j++ ){
nex = current->down;
if( current->row > nex->row ){
current->down = nex->down;
nex->down = current;
if( current == List_CHead->down ){
List_CHead->down = nex;
pre = List_CHead->down;
}
else{
pre->down = nex;
pre = pre->down;
}
}
else{
pre = current;
current = nex;
}
}
}
}</span>
<span style="font-size:12px;">//将current插入到其对应的行链表的合适位置,使行链表的元素按行号递增顺序排列
void Insert_R( OLNode *rhead, OLNode * current )
{
if( ( rhead + current->row )->right == NULL ){
( rhead + current->row )->right = current;
}
else{ //将当前项插入到对应行链表的第一项,并对其进行排序
current->right = ( rhead + current->row )->right;
( rhead + current->row )->right = current;
Increse_row( rhead + current->row );
}
}
//将current插入到其对应的列链表的合适位置,使列链表的元素按列号递增顺序排列
void Insert_C( OLNode *chead, OLNode * current )
{
if( ( chead + current->col )->down == NULL ){
( chead + current->col )->down = current;
}
else{ //将当前项插入到对应行链表的第一项,并对其进行排序
current->down = ( chead + current->col )->down;
( chead + current->col )->down = current;
Increse_col( chead + current->col );
}
}</span>
3、创建稀疏矩阵时我们同样需要矩阵的参数:详见《
稀疏矩阵的顺序存储结构表示》。
//创建稀疏矩阵,矩阵元素从第一行第一列开始没有第0行和第0列,矩阵的对应值的行列值即为实际的行列值
void CreateSMatrix_OL( CrossList *matrix )
{
int i;
OLNode *current;
printf( "please input the total rows of the matrix:\n" ); //输入矩阵的总行数
scanf( "%d", &matrix->row_num );
printf( "please input the total colums of the matrix:\n" );//输入矩阵的总列数
scanf( "%d", &matrix->col_num );
printf( "please input the total numbers of non_zero elements of the matrix:\n" ); //输入矩阵的非零值的个数
scanf( "%d", &matrix->non_zero );
if( matrix->non_zero > matrix->row_num * matrix->col_num ){ //输入的非零值个数太多报错并结束程序的执行
printf( "CreateSMatrix Error:too much none zero elements\n" );
exit( EXIT_FAILURE );
}
if( !Malloclist( matrix ) ){ //为matrix分配空间,若分配失败报错并结束执行
printf( "CreateSMatrix_OL Error:OVERFLOW!\n" );
exit( EXIT_FAILURE );
}
printf( "make sure: 0<row<=%d, 0<col<=%d\n", matrix->row_num, matrix->col_num );
for( i = 0; i < matrix->non_zero; i++ ){ //分配存储空间成功,以(行,列,值)的形式逐个输入矩阵的值
current = ( OLNode * )malloc( sizeof( OLNode ) ); //为一个矩阵项分配空间
if( !current ){ //分配存储空间失败,报错并结束程序执行
printf( "CreateSMatrix Error: OVERFLOW\n" );
exit( EXIT_FAILURE );
}
current->down = NULL; //将current的down和right都初始化为NULL
current->right = NULL;
printf( "please input the (row, colum, value) of the %d none zero element of the matrix:\n", i + 1 );
scanf( "%d,%d,%d", ¤t->row, ¤t->col, ¤t->value );
if( current->row <= 0 || current->row > matrix->row_num || current->col > matrix->col_num || current->col <= 0 ){ //当输入的行列值不满足条件时
printf( "CreateSMatrix Error:wrong input of row or col\n" ); //打印错误并结束程序执行
exit( EXIT_FAILURE );
}
//将current插入到对应的行列链表的合适的位置
Insert_R( matrix->rhead, current );
Insert_C( matrix->chead, current );
}
}
4、进行矩阵乘法时,由于即有行链表,又有列链表,我们可以很方便的对行列进行操作。用第一个矩阵的第i个行链表与第二矩阵的各列链表分别进行运算。不需要遍历第二个矩阵的所有项。只需要按照链表进行判断和运算。
<pre name="code" class="cpp">//当第一个矩阵的列值等于第二个矩阵的行值时,对这两个矩阵进行相乘的运算并将结果存在result矩阵中
void MultSMatrix_OL( CrossList *matrix1, CrossList *matrix2, CrossList *result )
{
int i;
int j;
int count;
OLNode *current1;
OLNode *current2;
OLNode *current;
if( matrix1->col_num != matrix2->row_num ){ //如果两个矩阵的行数或者列数不相同
printf( "AddSMatrix Error: the colum of the first matrix and therow of the second matrix is different!\n" );
}
else{
result->row_num = matrix1->row_num;
result->col_num = matrix2->col_num;
if( !Malloclist( result ) ){//为结果矩阵分配内存空间
printf( "CopySMatrix_OL Error:OVERFLOW!\n" );
exit( EXIT_FAILURE );
}
i = 1;
count = 0;
while( i <= matrix1->row_num ){ //对第一个矩阵逐行进行运算
j = 1;
while( j <= matrix2->col_num ){ //用第一个矩阵的第i个行链表的各项依次与第二个矩阵的各个列链表的各项进行合适的运算
current1 = ( matrix1->rhead + i )->right;
current2 = ( matrix2->chead + j )->down;
current = ( OLNode * )malloc( sizeof( OLNode ) ); //为一个矩阵项分配空间
if( !current ){ //分配存储空间失败,报错并结束程序执行
printf( "SubSMatrix_OL Error: OVERFLOW\n" );
exit( EXIT_FAILURE );
}
else{ //分配存储空间成功呢,初始化
current->value = 0;
current->right = NULL;
current->down = NULL;
}
while( current1 && current2 ){ //当第一个矩阵的第i个行链表和第二个矩阵的第j个列链表均没有到达末尾时
if( current1->col == current2->row ) { //如果current1的列号与current2的行号相等
current->row = current1->row; //current1的行号和current2的列号构成结果矩阵项current的行列号
current->col = current2->col;
current->value += current1->value * current2->value;//current1和current2相乘的结果作为current的value值
current1 =current1->right;//继续下一项运算
current2 = current2->down;
}
else if( current1->col < current2->row ) //如果current1的列号小于current2的行号
current1 =current1->right; //取current1的下一项进行比较运算
else //如果current1的列号大于current2的行号
current2 = current2->down; //取current2的下一项进行比较运算
}
if( current->value == 0 ){ //如果current的value的最终结果为0,说明第一个矩阵的第i行中的所有元素的列号都与
free( current ); //第二个矩阵的第j列中的所有元算的行号都不想等,即没有满足相乘条件的项,释放current
}
else{ //如果current的value的最终结果不为0,则将current插入到结果矩阵的合适位置
Insert_R( result->rhead, current );
Insert_C( result->chead, current );
count++; 每插入到结果矩阵中一个矩阵项,count就增加1,用于统计结果矩阵的非零值的个数
}
j++;
}
i++;
}
result->non_zero = count;
}
}