文章目录
一、概述
本次实验的目的是用十字链表和一般方法分别实现稀疏矩阵的加法和乘法,并比较两种方法的优缺点。稀疏矩阵是指矩阵中大部分元素为零,只有少数元素非零的矩阵。稀疏矩阵在科学计算、图像处理、数据挖掘等领域有广泛的应用。为了节省存储空间和提高运算效率,稀疏矩阵通常采用特殊的存储结构来表示,其中一种常用的结构是三元组表,即用一个一维数组存储非零元素的行号、列号和值,以及矩阵的行数、列数和非零元个数。另一种常用的结构是十字链表,即用一个二维链表存储非零元素的信息,每个结点包含行号、列号、值以及指向同一行下一个结点和同一列下一个结点的指针,同时用两个指针数组分别存储每一行和每一列的第一个结点的地址,以及矩阵的基本信息。
本次实验中,我们使用c语言编写了两个程序,分别用三元组表和十字链表实现了稀疏矩阵的加法和乘法,并对程序进行了测试和分析。我们主要关注以下几个方面:
函数调用:如何设计函数接口,如何传递参数,如何返回结果;
算法设计:如何实现稀疏矩阵的创建、加法、乘法等操作,如何处理边界情况,如何优化算法性能;
数据结构:如何定义数据类型,如何分配内存空间,如何管理指针;
时间复杂度:如何估计算法执行时间,如何比较不同方法的效率;
结构特点:如何分析三元组表和十字链表各自的优缺点,如何选择合适的存储结构。
本次实验是数据结构实验中的一项,旨在通过使用十字链表表示稀疏矩阵以及实现稀疏矩阵的加法运算,加深对数据结构和算法的理解和应用。本实验采用C语言编写程序,使用三元组表表示稀疏矩阵,并根据输入的矩阵数据进行加法运算,输出结果。
二、具体题目分析
1. 三元组表实现稀疏矩阵加法
1.1 函数调用
我们设计了以下四个函数来实现三元组表表示的稀疏矩阵加法:
void CreateMatrix(TSMatrix *T):创建三元组表,参数为指向TSMatrix类型(定义见下文)的指针,通过修改原地址上的值来实现矩阵的创建。该函数从标准输入读取矩阵的行数、列数、非零元个数和每个非零元的行号、列号和值,并存储在三元组表中。该函数还更新了矩阵的最大行号和最大列号。
void AddMatrix(TSMatrix T1, TSMatrix T2, TSMatrix *T3):加合三元组表,参数为两个TSMatrix类型的值和一个指向TSMatrix类型的指针,分别表示两个输入矩阵和一个输出矩阵。该函数实现了两个矩阵相加的逻辑,并将结果存储在第三个矩阵中。该函数还更新了输出矩阵的行数、列数和非零元个数。
void PrintMatrix(TSMatrix T3):打印三元组表,参数为一个TSMatrix类型的值,表示要输出的矩阵。该函数遍历三元组表中的每个非零元,并按照行号、列号和值的顺序输出到标准输出。
void RankMatrix(int e[], int r[], int c[]):给表排序,参数为三个整型数组,分别存储非零元素值,行号和列号。该函数对结果矩阵按行列顺序进行排序,使用冒泡排序算法。
1.2 算法设计
我们使用以下算法来实现三元组表表示的稀疏矩阵加法:
创建三元组表:首先分配内存空间给指针数组,然后从标准输入读取矩阵的基本信息,接着循环读取每个非零元素,并将其存储在三元组表中,同时更新最大行号和最大列号。
加合三元组表:首先定义三个临时数组,用于存储结果矩阵中非零元素值,行号和列号。然后循环遍历两个输入矩阵中每个非零元素,如果两个非零元素在同一位置(行号和列号相同),则将它们的值相加,并存储在临时数组中,并将该位置在两个输入矩阵中都标记为已处理;如果只有一个输入矩阵中有该位置的非零元素,则直接将其复制到临时数组中,并将该位置在对应输入矩阵中标记为已处理。最后将临时数组中的数据按行列顺序排序,并转存到输出矩阵中。
打印三元组表:循环遍历三元组表中每个非零元素,并按照格式输出到标准输出。
给表排序:使用冒泡排序算法对临时数组中的数据按行列顺序进行排序,即先比较行号,如果相同则比较列号,如果不同则交换位置。
1.3 数据结构
我们使用以下数据结构来表示三元组表:
typedef struct
{
int row, col; //行号和列号
int e; //值
}Triple; //定义三元组结构体
typedef struct
{
Triple data[MAXSIZE+1]; //非零元三元组表,data[0]未用,最多可以存储MAXSIZE个非零元素
int m, n, len; //矩阵的行数,列数和非零元个数
}TSMatrix; //定义统计结构表的附加信息,用于存储一个稀疏矩阵的基本信息
1.4 时间复杂度
我们估计算法执行时间的方法是统计算法中基本操作(如赋值、比较、加法等)执行次数的数量级,并用大O符号表示。
创建三元组表:该算法中基本操作是赋值操作,执行次数与非零元个数成正比,即O(t),其中t是非零元个数。
加合三元组表:该算法中基本操作是比较操作,执行次数与两个输入矩阵中非零元个数之积成正比,即O(t1t2),其中t1和t2分别是两个输入矩阵的非零元个数。
打印三元组表:该算法中基本操作是输出操作,执行次数与输出矩阵中非零元个数成正比,即O(t3),其中t3是输出矩阵的非零元个数。
给表排序:该算法中基本操作是比较操作,执行次数与输出矩阵中非零元个数之平方成正比,即O(t3^2),其中t3是输出矩阵的非零元个数。
综上所述,整个程序的时间复杂度为O(t+t1t2+t3+t3^2),其中t是创建时输入的非零元个数,t1和t2分别是两个输入矩阵的非零元个数,t3是输出矩阵的非零元个数。
综上所述,整个程序的时间复杂度为O(t+t1*t2+t3+t3^2),其中t是创建时输入的非零元个数,t1和t2分别是两个输入矩阵的非零元个数,t3是输出矩阵的非零元个数。
1.5 结构特点
三元组表表示的稀疏矩阵有以下特点:
优点:存储空间节省,只需存储非零元素和矩阵的基本信息,不需要为零元素分配空间;存储结构简单,只需一个一维数组即可表示一个稀疏矩阵,不需要额外的指针或链表;存取操作方便,只需按下标访问数组元素即可获取或修改非零元素的信息。
缺点:运算效率低,需要遍历整个数组才能找到相同位置的非零元素进行运算,且运算结果可能需要重新排序;存储空间浪费,如果矩阵中非零元素较多或分布不均匀,则数组中可能会有很多空闲位置;存储结构不灵活,如果矩阵中非零元素发生变化,则可能需要重新分配数组空间或调整数组长度。
2. 十字链表实现稀疏矩阵加法
2.1 函数调用
我们设计了以下四个函数来实现十字链表表示的稀疏矩阵加法:
void Create_CrossList(CrossList *L,int m,int n,int t):创建十字链表,参数为指向CrossList类型(定义见下文)的指针和三个整型变量,分别表示矩阵的行数、列数和非零元个数。该函数从标准输入读取每个非零元的行号、列号和值,并将其插入到十字链表中。该函数还更新了矩阵的基本信息。
void AddMatrix(CrossList *A,CrossList *B):加合十字链表,参数为两个指向CrossList类型的指针,分别表示两个输入矩阵和一个输出矩阵。该函数实现了两个矩阵相加的逻辑,并将结果存储在第一个矩阵中。该函数还更新了输出矩阵的基本信息。
void print_result(CrossList *A):打印十字链表,参数为一个指向CrossList类型的指针,表示要输出的矩阵。该函数遍历十字链表中的每个非零元,并按照行号、列号和值的顺序输出到标准输出。
void Insert(CrossList *A,OList node):插入结点,参数为一个指向CrossList类型的指针和一个OList类型(定义见下文)的值,分别表示要插入的矩阵和要插入的结点。该函数实现了将一个结点插入到一个十字链表中的逻辑,并处理了可能出现的添加、删除、合并等情况。
2.2 算法设计
我们使用以下算法来实现十字链表表示的稀疏矩阵加法:
创建十字链表:首先分配内存空间给指针数组,然后从标准输入读取每个非零元素,并创建一个新结点来存储其信息。接着根据行号和列号将新结点插入到对应行和列的链表中,如果该位置已有结点,则按顺序排列。最后更新矩阵的基本信息。
加合十字链表:首先遍历第二个输入矩阵中每一行的第一个结点,并将其复制一份作为临时结点。然后调用插入结点函数将临时结点插入到第一个输入矩阵中,并处理可能出现的添加、删除、合并等情况。最后更新输出矩阵的基本信息。
打印十字链表:遍历第一个输入矩阵中每一行的每一个结点,并按格式输出到标准输出。
插入结点:根据要插入的结点的行号和列号,在第一个输入矩阵中寻找相同位置或相邻位置的结点,并进行比较。
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 1000
/*noj,数据结构实验2.2稀疏矩阵加法
* 采用三元组表,并从1开始计数
* 对相同行列的元素进行加和并合并
* 编译:gcc 2.2稀疏矩阵加法.c -o 2.2稀疏矩阵加法.exe
* 运行:./2.2稀疏矩阵加法.exe
* 输入:
* 3 4 3 2
* 1 1 1
* 1 3 1
* 2 2 2
* 1 2 1
* 2 2 3
* 输出:
* 1 1 1
* 1 2 1
* 1 3 1
* 2 2 5
*/
int l=0; //长度计数全局变量
typedef int ElemType; //定义元素类型
typedef struct
{
int row, col;
ElemType e;
}Triple; //定义三元组表结构体
typedef struct
{
Triple data[MAXSIZE+1];
int m, n, len;
}TSMatrix; //统计结构表的附加信息
void CreatMatrix(TSMatrix *T); //创建三元组表
void AddMatrix(TSMatrix T1, TSMatrix T2, TSMatrix *T3); //加合三元组表
void PrintMatrix(TSMatrix T3); //打印三元组表T3
void RankMatrix(int e[], int r[], int c[]); //给表排序
int main()
{
TSMatrix T1, T2, T3;
int t1, t2;
scanf("%d %d %d %d", &T1.m, &T1.n, &t1, &t2);
T2.m=T1.m;
T2.n=T1.n;
T1.len=t1;
T2.len=t2;
CreatMatrix(&T1); //创建三元组表T1
CreatMatrix(&T2); //创建三元组表T2
AddMatrix(T1, T2, &T3); //加合三元组表
PrintMatrix(T3); //打印三元组表T3
return 0;
}
void CreatMatrix(TSMatrix *T)
{ /*创建三元组表 */
T->m=T->n=0;
for(int i=1;i<=T->len;i++)
{
scanf("%d %d %d",&T->data[i].row, &T->data[i].col, &T->data[i].e);
if(T->m < T->data[i].row) T->m=T->data[i].row;
if(T->n < T->data[i].col) T->n=T->data[i].col;
}
}
void AddMatrix(TSMatrix T1, TSMatrix T2, TSMatrix *T3)
{ /*加合三元组表 */
int r[MAXSIZE], c[MAXSIZE], e[MAXSIZE];
int k=1;
for(int i=1; i<=T1.len; i++)
{
for(int j=1; j<=T2.len; j++)
{
if(T1.data[i].row==T2.data[j].row && T1.data[i].col==T2.data[j].col)
{
e[k]=T1.data[i].e+T2.data[j].e;
r[k]=T1.data[i].row;
c[k]=T1.data[i].col;
k++;
T1.data[i].col=-1;
T2.data[j].col=-1;
}
}
}
for(int i=1; i<=T1.len; i++)
{
if(T1.data[i].col==-1) //判断是否重复写入
{
continue;
}
e[k]=T1.data[i].e;
r[k]=T1.data[i].row;
c[k]=T1.data[i].col;
k++;
T1.data[i].col=-1;
}
for(int i=1; i<=T2.len; i++)
{
if(T2.data[i].col==-1) //判断是否重复写入
{
continue;
}
e[k]=T2.data[i].e;
r[k]=T2.data[i].row;
c[k]=T2.data[i].col;
k++;
T2.data[i].col=-1;
}
r[k]=-2;
RankMatrix(e, r, c);
for(int i=1;r[i]!=-2;i++)
{
T3->data[i].e=e[i];
T3->data[i].col=c[i];
T3->data[i].row=r[i];
l++;
}
}
void RankMatrix(int e[], int r[], int c[])
{ /*给表排序*/
int tempr, tempc, tempe;
for(int i=1; ;i++)
{
if(r[i]==-2) break;
for(int j=i+1; ;j++)
{
if(r[j]==-2) break; //判断是否超过表长
if(r[i] > r[j]) //冒泡排序
{
tempr=r[i];
tempc=c[i];
tempe=e[i];
r[i]=r[j];
c[i]=c[j];
e[i]=e[j];
r[j]=tempr;
c[j]=tempc;
e[j]=tempe;
}
else if(r[i]==r[j])
{
if(c[i] > c[j]) //冒泡排序
{
tempc=c[i];
tempe=e[i];
c[i]=c[j];
e[i]=e[j];
c[j]=tempc;
e[j]=tempe;
}
if(c[i]==c[j])
printf("error");
}
}
}
}
void PrintMatrix(TSMatrix T3)
{ /*打印三元组表T3 */
for(int i=1; i<=l; i++)
{
if(T3.data[i].e==0) continue; //删除e=0的数据
printf("%d %d %d\n", T3.data[i].row, T3.data[i].col, T3.data[i].e);
}
}
三、收获总结
通过本次实验,我们收获了以下几点:
1、掌握了稀疏矩阵的概念和应用场景,理解了稀疏矩阵与普通矩阵在存储和运算上的区别;
2、掌握了三元组表和十字链表两种稀疏矩阵的存储结构,理解了它们各自的优缺点;
3、掌握了用三元组表和十字链表分别实现稀疏矩阵加法和乘法的算法设计思路和具体步骤;
4、掌握了用c语言编写稀疏矩阵相关程序的技巧和方法,包括函数调用、参数传递、结果返回、数据类型定义、内存空间分配、指针管理等;
5、掌握了估计算法时间复杂度的方法和公式,比较了三元组表和十字链表在稀疏矩阵加法和乘法上的效率差异;
6、掌握了选择合适存储结构的原则和依据,根据不同问题和需求选择最优化的解决方案。
首先,我加深了对稀疏矩阵的理解。稀疏矩阵是指大部分元素为零的矩阵,通过使用三元组表的方式存储和表示稀疏矩阵,可以有效减少存储空间和提高运算效率。 其次,我学会了使用十字链表表示稀疏矩阵。十字链表是一种基于链表的数据结构,可以灵活地表示稀疏矩阵中的非零元素及其位置信息,便于后续的加法运算。 最后,我掌握了稀疏矩阵的加法运算算法。通过遍历两个稀疏矩阵的三元组表,将相同位置的元素相加并合并,得到结果矩阵的三元组表表示,实现了稀疏矩阵的加法运算。