基于十字链表与三元组表的稀疏矩阵压缩存储实例研究
数据结构实验五矩阵的压缩存储与运算学习资料

数据结构实验五矩阵的压缩存储与运算第五章矩阵的压缩存储与运算【实验目的】1. 熟练掌握稀疏矩阵的两种存储结构(三元组表和十字链表)的实现;2. 掌握稀疏矩阵的加法、转置、乘法等基本运算;3. 加深对线性表的顺序存储和链式结构的理解。
第一节知识准备矩阵是由两个关系(行关系和列关系)组成的二维数组,因此对每一个关系上都可以用线性表进行处理;考虑到两个关系的先后,在存储上就有按行优先和按列优先两种存储方式,所谓按行优先,是指将矩阵的每一行看成一个元素进行存储;所谓按列优先,是指将矩阵的每一列看成一个元素进行存储;这是矩阵在计算机中用一个连续存储区域存放的一般情形,对特殊矩阵还有特殊的存储方式。
一、特殊矩阵的压缩存储1. 对称矩阵和上、下三角阵若n阶矩阵A中的元素满足= (0≤i,j≤n-1 )则称为n阶对称矩阵。
对n阶对称矩阵,我们只需要存储下三角元素就可以了。
事实上对上三角矩阵(下三角部分为零)和下三角矩阵(上三角部分为零),都可以用一维数组ma[0.. ]来存储A的下三角元素(对上三角矩阵做转置存储),称ma为矩阵A的压缩存储结构,现在我们来分析以下,A和ma之间的元素对应放置关系。
问题已经转化为:已知二维矩阵A[i,j],如图5-1,我们将A用一个一维数组ma[k]来存储,它们之间存在着如图5-2所示的一一对应关系。
任意一组下标(i,j)都可在ma中的位置k中找到元素m[k]= ;这里:k=i(i+1)/2+j (i≥j)图5-1 下三角矩阵a00 a10 a11 a20 … an-1,0 … an-1,n-1k= 0 1 2 3 …n(n-1)/2 …n(n+1)/2-1图5-2下三角矩阵的压缩存储反之,对所有的k=0,1,2,…,n(n+1)/2-1,都能确定ma[k]中的元素在矩阵A中的位置(i,j)。
这里,i=d-1,(d是使sum= > k的最小整数),j= 。
2. 三对角矩阵在三对角矩阵中,所有的非零元素集中在以主对角线为中心的带内状区域中,除了主对角线上和直接在对角线上、下方对角线上的元素之外,所有其它的元素皆为零,见图5-3。
数据结构之稀疏矩阵稀疏矩阵的存储方式和操作分析

数据结构之稀疏矩阵稀疏矩阵的存储方式和操作分析稀疏矩阵是指矩阵中大部分元素为零的特殊矩阵。
在实际应用中,稀疏矩阵经常出现,如图像处理、网络分析和科学计算等领域。
对于稀疏矩阵的存储和操作是数据结构中的重要内容。
本文将介绍稀疏矩阵的存储方式和相关操作的分析。
一、稀疏矩阵存储方式稀疏矩阵的存储方式有多种,其中三元组顺序表和二维数组是比较常用的方法。
1. 三元组顺序表三元组顺序表是一种基于行优先存储的方式,可以将稀疏矩阵以非零元素的形式存储起来。
主要包括行号、列号和元素值三个信息。
以一个4x5的稀疏矩阵为例,其中有三个非零元素分别为A[1][2]=3, A[2][3]=4, A[3][4]=5。
可以使用三元组顺序表来存储:```行号列号元素值1 2 32 3 43 4 5```三元组顺序表的优点是可以节省存储空间,同时也方便进行矩阵的操作。
但是在进行元素的查找和修改时,效率较低。
2. 二维数组二维数组是一种常见的矩阵表示方法,可以直接使用二维数组来表示稀疏矩阵。
其中非零元素的位置用实际的值表示,其余位置用零值表示。
以同样的4x5的稀疏矩阵为例,使用二维数组存储如下:```0 0 0 0 00 0 3 0 00 0 0 4 00 0 0 0 5```二维数组的优点是简单直观,并且可以快速进行元素的查找和修改。
但当稀疏矩阵的规模较大时,会造成较高的存储资源浪费。
二、稀疏矩阵的操作分析对于稀疏矩阵的操作,主要包括矩阵的转置、相加、相乘等。
1. 转置操作稀疏矩阵的转置是指将原始矩阵的行与列对调。
对于三元组顺序表来说,转置操作主要涉及到行号和列号的交换。
而对于二维数组来说,可以直接在取值的时候将行号和列号对调即可。
2. 相加操作稀疏矩阵的相加操作是指将两个矩阵对应位置的元素相加。
对于三元组顺序表来说,可以通过遍历两个矩阵的非零元素,并将其对应位置的元素相加。
而对于二维数组来说,可以直接将对应位置的元素相加即可。
3. 相乘操作稀疏矩阵的相乘操作是指将两个矩阵相乘得到一个新的矩阵。
稀疏矩阵引用十字链表运算

稀疏矩阵应用摘要本课程设计主要实现在三元组存储结构与十字链表存储结构下输入稀疏矩阵,并对稀疏矩阵进行转置,相加,相乘操作,最后输出运算后的结果。
在程序设计中,考虑到方法的难易程度,采用了先用三元组实现稀疏矩阵的输入,输出,及其转置,相加,相乘操作的方法,再在十字链表下实现。
程序通过调试运行,结果与预期一样,初步实现了设计目标。
关键词程序设计;稀疏矩阵;三元组;十字链表1 引言•课程设计任务本课程设计主要实现在三元组存储结构与十字链表存储结构下输入稀疏矩阵,并对稀疏矩阵进行转置,相加,相乘操作,最后输出运算后的结果。
稀疏矩阵采用三元组和十字链表表示,并在两种不同的存储结构下,求两个具有相同行列数的稀疏矩阵A和B的相加矩阵C,并输出C;求出A的转置矩阵D,输出D;求两个稀疏矩阵A和B的相乘矩阵E,并输出E。
•课程设计性质数据结构课程设计是重要地实践性教学环节。
在进行了程序设计语言课和《数据结构》课程教学的基础上,设计实现相关的数据结构经典问题,有助于加深对数据结构课程的认识。
本课程设计是数据结构中的一个关于稀疏矩阵的算法的实现,包括在三元组和十字链表下存储稀疏矩阵,并对输入的稀疏矩阵进行转置,相加,相乘等操作,最后把运算结果输出。
此课程设计要求对数组存储结构和链表存储结构非常熟悉,并能熟练使用它们。
1.3课程设计目的其目的是让我们在学习完C、数据结构等课程基础上,掌握多维数组的逻辑结构和存储结构、掌握稀疏矩阵的压缩存储及转置,相加,相乘等基本操作,并用不同的方法输出结果,进一步掌握设计、实现较大系统的完整过程,包括系统分析、编码设计、系统集成、以及调试分析,熟练掌握数据结构的选择、设计、实现以及操作方法,为进一步的应用开发打好基础。
•需求分析2.1设计函数建立稀疏矩阵及初始化值和输出稀疏矩阵的值本模块要求设计函数建立稀疏矩阵并初始化,包括在三元组结构下和十字链表结构下。
首先要定义两种不同的结构体类型,在创建稀疏矩阵时,需要设计两个不同的函数分别在三元组和十字链表下创建稀疏矩阵,在输入出现错误时,能够对错误进行判别处理,初始化稀疏矩阵都为空值,特别注意在十字链表下,对变量进行动态的地址分配。
稀疏矩阵的存储和快速转置实验报告

福建工程学院课程设计课程:数据结构题目:稀疏矩阵的快速转置专业:运算机类班级:座号:姓名:2021年6月25日实验题目:稀疏矩阵的快速转置一、要解决的问题利用三元组表存储稀疏矩阵,利用快速转置算法进行转置,并输出转置之前和以后的三元组表和矩阵。
二、算法大体思想描述:由于稀疏矩阵的非零元素较少,零元素较多,因此只需存储其非零元素。
因此能够成立一个三元组表,别离保存稀疏矩阵的非零元素的行号、列号和元素值。
对稀疏矩阵进行快速转置是能够引入两个向量num[n+1],cpot[n+1],别离标记矩阵中第col列的非零元素个数和第一个非零元素在转置后的矩阵的位置;再扫描三元组表,找到非零元素,直接对其在转置后的矩阵所在的位置上进行修改,以节省时刻。
三、详细设计⒈元素类型,结点类型typedef struct {int i,j;int e;}Triple;typedef struct{Triple data[MAXSIZE+1];int mu,nu,tu;} Tsmatrix;2.对抽象数据类型中的部份大体操作的伪码算法如下:Tsmatrix * creatarray(Tsmatrix *M){ int m,n,p=1;int c;printf("please input the array A:\n");for(m=1;m<=a;m++)for(n=1;n<=b;n++){ scanf("%d",&c);if(c!=0){ M->data[p].e=c;M->data[p].i=m;M->data[p].j=n;p++;}}M->tu=p; M->mu=a; M->nu=b;printf("yuan lai san yuan zu de biao shi wei :\n\n");for(m=1;m<=M->tu;m++)printf("%3d%3d%3d\t",M->data[m].i,M->data[m].j,M->data[m].e);printf("\n");return M;}/*三元组快速转置*/Tsmatrix * fasttrans(Tsmatrix *M,Tsmatrix *T){ int p,col,q,t,m;int num[100];int cpot[100];T->mu=M->nu; T->nu=M->mu; T->tu=M->tu;if(T->tu!=0){for(col=1;col<=M->nu;col++) num[col]=0;for(t=1;t<=M->tu;t++) ++num[M->data[t].j];cpot[1]=1;for(col=2;col<=M->nu;col++) cpot[col]=cpot[col-1]+num[col-1];for(p=1;p<=M->tu;++p){ col=M->data[p].j; q=cpot[col];T->data[q].i=M->data[p].j;T->data[q].j=M->data[p].i;T->data[q].e=M->data[p].e;++cpot[col];}}printf("\n\nzhuan zhi hou de san yuan zu biao shi wei :\n\n");for(m=1;m<=T->tu;m++)printf("%3d%3d%3d\t",T->data[m].i,T->data[m].j,T->data[m].e);printf("\n");return T;}/*输出三元组函数*/void print(Tsmatrix *T,int x,int y){ int m,n,p=1;int d;for(m=1;m<=x;m++){ printf("\n");for(n=1;n<=y;n++){ if(T->data[p].i==m&&T->data[p].j==n){ d=T->data[p].e;p++;}else d=0;printf("%6d",d);}}}}3.主函数和其他函数的伪码算法void main(){ Tsmatrix *M,*T;M=(Tsmatrix *)malloc(sizeof(Tsmatrix));T=(Tsmatrix *)malloc(sizeof(Tsmatrix));printf("please input array's row and col:\n");scanf("%d%d",&a,&b); /*输入行列数*/ M=creatarray(M); /*创建稀疏矩阵*/printf("you had creat the array:\n");print(M,a,b); /*输出创建好的三元组*/T=fasttrans(M,T); /*将三元组转置*/printf("the trans array is:\n");print(T,b,a);getch();}4、模块结构及功能}四、源程序清单:#include<>#define MAXSIZE 100typedef struct {int i,j;int e;}Triple;typedef struct{Triple data[MAXSIZE+1];int mu,nu,tu;} Tsmatrix;int a,b; /*概念全局变量数组的行数a和列数b*//*用数组创建三元组*/Tsmatrix * creatarray(Tsmatrix *M){ int m,n,p=1;int c;printf("please input the array A:\n");for(m=1;m<=a;m++)for(n=1;n<=b;n++){ scanf("%d",&c);if(c!=0){ M->data[p].e=c;M->data[p].i=m;M->data[p].j=n;p++;}}M->tu=p; M->mu=a; M->nu=b;printf("yuan lai san yuan zu de biao shi wei :\n\n");for(m=1;m<M->tu;m++)printf("%3d%3d%3d\t",M->data[m].i,M->data[m].j,M->data[m].e);printf("\n");return M;}/*三元组快速转置*/Tsmatrix * fasttrans(Tsmatrix *M,Tsmatrix *T){ int p,col,q,t,m;int num[100];int cpot[100];T->mu=M->nu; T->nu=M->mu; T->tu=M->tu;if(T->tu!=0){for(col=1;col<=M->nu;col++) num[col]=0;for(t=1;t<=M->tu;t++) ++num[M->data[t].j];cpot[1]=1;for(col=2;col<=M->nu;col++) cpot[col]=cpot[col-1]+num[col-1];for(p=1;p<=M->tu;++p){ col=M->data[p].j; q=cpot[col];T->data[q].i=M->data[p].j;T->data[q].j=M->data[p].i;T->data[q].e=M->data[p].e;++cpot[col];}}printf("\n\nzhuan zhi hou de san yuan zu biao shi wei :\n\n");for(m=1;m<T->tu;m++)printf("%3d%3d%3d\t",T->data[m].i,T->data[m].j,T->data[m].e);printf("\n");return T;}/*输出三元组函数*/void print(Tsmatrix *T,int x,int y){ int m,n,p=1;int d;for(m=1;m<=x;m++){ printf("\n");for(n=1;n<=y;n++){ if(T->data[p].i==m&&T->data[p].j==n){ d=T->data[p].e;p++;}else d=0;printf("%6d",d);}}}void main(){ Tsmatrix *M,*T;M=(Tsmatrix *)malloc(sizeof(Tsmatrix));T=(Tsmatrix *)malloc(sizeof(Tsmatrix));printf("please input array's row and col:\n");scanf("%d%d",&a,&b); /*输入行列数*/M=creatarray(M);printf("you had creat the array:\n");print(M,a,b);T=fasttrans(M,T);printf("the trans array is:\n");print(T,b,a);getch();}五、测试数据及测试结果:(1)我输入的稀疏矩阵为:(2)回车显示的结果是:六、课程设计总结及心得体会:通过本次课程设计,我对有关稀疏矩阵及其三元组表的知识做了温习和巩固。
稀疏矩阵的十字链表存储

稀疏矩阵的⼗字链表存储稀疏矩阵的压缩存储有⼏种⽅式,如:三元组顺序表、⾏逻辑链接的顺序表和⼗字链表。
使⽤链表存储的好处是:便于矩阵中元素的插⼊和删除。
例如:“将矩阵B加到矩阵A上”,那么矩阵A存储的元素就会有变动。
⽐如会增加⼀些⾮零元,或者删除⼀些元素(因为b ij+a ij=0)。
下图是矩阵M和M的⼗字链表存储:⼗字链表及其结点可⽤如下结构体表⽰:typedef struct OLNode{int i, j; // ⾮零元的⾏列下标ElemType e;struct OLNode *right, *down; // 向右域和向下域} OLNode, *OLink;typedef struct{OLink *rhead, *chead; // ⾏链表和列链表的头指针数组int mu, nu, tu; // 稀疏矩阵的⾏数、列数和⾮零元个数} CrossList;在通过代码创建⼗字链表时,要特别注意right、down和rhead、chead这些指针的赋值。
现在来看“将矩阵B加到矩阵A上”这个问题。
所要做的操作:a ij +b ij,其结果⼀共会有4种情况:1. a ij(b ij = 0)(不做变化)2. b ij(a ij = 0)(在A中插⼊⼀个新结点)3. a ij +b ij ≠ 0 (改变结点a ij的值域)4. a ij +b ij = 0 (删除结点a ij)假设指针pa和pb分别指向矩阵A和B中⾏值相同的两个结点,对于上述4种情况的处理过程为:1. 若“pa == NULL”或“pa->j⼤于pb->j”,则在矩阵A中插⼊⼀个值为b ij的结点。
并且需要修改同⼀⾏前⼀结点的right指针,和同⼀列前⼀结点的down指针。
2. 若“pa->j⼩于pb-j”,则pa指针右移⼀步。
3. 若“pa->j等于pb-j”,并且“pa->e + pb->e != 0”,则修改pa->e即可。
稀疏矩阵的压缩存储方法

稀疏矩阵的压缩存储方法稀疏矩阵是指矩阵中绝大部分元素为0的矩阵。
在现实生活中,很多矩阵都是稀疏的,比如图像处理中的二值化图像、网络连接矩阵等。
由于稀疏矩阵中大部分元素为0,对其进行存储和计算会消耗大量的存储空间和计算资源。
因此,对稀疏矩阵进行压缩存储是一种有效的方式,可以节省存储空间、提高计算效率。
一般来说,存储稀疏矩阵的方法有三种:顺序存储法、链式存储法和三元组顺序表法。
其中,三元组顺序表法是最常用的一种方法。
三元组顺序表法是将稀疏矩阵的非零元素按照行、列和值三个维度进行存储。
具体来说,可以定义一个结构体来表示一个非零元素,包括行号、列号和值三个属性。
然后,可以使用一个一维数组来存储所有的非零元素,数组的长度为非零元素的个数。
这样,就可以通过遍历数组来获取稀疏矩阵的所有非零元素。
三元组顺序表法的优点是存储简单、操作灵活。
由于只存储非零元素,可以大大减少存储空间的占用。
而且,可以根据需要对非零元素进行插入、删除等操作,非常方便。
除了三元组顺序表法,还有其他的压缩存储方法。
比如,可以使用行逻辑链接法,将矩阵的每一行都看作一个链表的节点,节点中包含列号和值两个属性。
通过将每一行的节点连接起来,就可以得到一个链表,从而实现稀疏矩阵的存储和操作。
这种方法相对简单,但是在获取某个元素时效率较低。
还可以使用十字链表法来存储稀疏矩阵。
十字链表法是在三元组顺序表法的基础上进行改进,将矩阵的行和列分别看作两个链表,链表中的节点包含行号、列号和值三个属性。
通过将行链表和列链表进行交叉连接,就可以得到一个十字链表。
这种方法可以提高获取某个元素的效率,但是相应地增加了存储空间的占用。
稀疏矩阵的压缩存储方法是一种优化矩阵存储和计算的有效方式。
三元组顺序表法是最常用的一种方法,可以通过一个一维数组存储稀疏矩阵的非零元素。
此外,还可以使用行逻辑链接法和十字链表法来存储稀疏矩阵。
不同的方法适用于不同的场景,可以根据实际需求选择合适的压缩存储方法。
稀疏矩阵压缩存储方法

稀疏矩阵压缩存储方法嘿,朋友们!今天咱来唠唠这个稀疏矩阵压缩存储方法。
你说啥是稀疏矩阵呀?就好比你有一大筐苹果,大部分都是空的位置,只有那么几个苹果在那,这就很稀疏嘛!那对于这样的矩阵,咱要是傻乎乎地全存起来,多浪费空间呀,就像你非得把那一大筐空位置也当宝贝一样存着。
这时候压缩存储方法就闪亮登场啦!它就像是个聪明的整理大师,能把那些重要的、有实际意义的元素好好地归置起来,而那些没啥用的空位置就不管啦。
比如说有一种方法叫三元组顺序表。
想象一下,这就像是给每个重要的元素都贴上一个小标签,标签上写着它在哪行哪列,还有它的值是多少。
这样不就把关键信息都抓住了嘛,还不占太多地方。
还有一种叫十字链表法呢!这就好比是给矩阵里的元素织了一张特别的网,把它们都串起来,让它们之间的关系变得清清楚楚。
这样找起元素来可方便啦,就跟你在一堆杂物里能快速找到你想要的东西一样。
咱为啥要用这压缩存储方法呀?你想想,要是数据量超级大,那得占多少存储空间呀!这就好比你有一个超级大的房间,要是不懂得合理利用空间,那不是浪费嘛!用了这方法,就像给房间做了个巧妙的收纳,一下子就整洁多了。
而且呀,在处理数据的时候,这压缩存储方法可太有用啦!能让计算速度变快,就像给你的电脑加了个小火箭,跑得飞快。
你说这多好呀!既节省了空间,又提高了效率。
这就像是你出门旅行,只带了最必要的东西,又轻松又方便。
所以呀,可别小瞧了这稀疏矩阵压缩存储方法,它可是数据处理里的一把好手呢!它能让我们更高效地处理那些看似杂乱无章的数据,让它们变得井井有条。
咱可得好好利用起来,让我们的工作和学习变得更轻松、更高效呀!这难道不是很值得我们去研究和掌握的吗?原创不易,请尊重原创,谢谢!。
稀疏矩阵的十字链表存储的思路

稀疏矩阵的十字链表存储的思路
稀疏矩阵是一种大多数元素都为0的矩阵,而十字链表是一种常
见的数据结构,用于存储稀疏矩阵。
十字链表提供了一种有效的方法,可以对矩阵进行高效的访问和操作。
十字链表的存储方式是将矩阵分成两个链表:行链表和列链表。
行链表和列链表中的每个节点都存储了一个非零元素和该元素的行编
号和列编号。
同时,每个节点也包含了指向下一个相同行或相同列的
节点的指针。
在十字链表中,每个节点都可以快速找到它所处的行和列以及与
它相邻的元素。
这使得我们可以在矩阵中进行诸如插入、删除、修改
及查找等操作。
在操作过程中,我们可以通过指针操作找到相邻的非
零元素,从而充分利用稀疏矩阵的特殊性质。
对于一个稀疏矩阵,我们需要将每一个非零元素存储到十字链表中。
首先,我们需要创建一个新的节点,并为它分配内存空间。
然后,我们需要将该节点插入到正确的位置,即行链表和列链表中。
插入操作的基本思路是,我们首先遍历行链表,找到该元素所在
的行号。
然后在该行号的节点中,按照列号的大小插入该新节点。
同时,我们需要在列链表中找到该元素所在的列号,并在该列号的节点
中插入新节点。
除了插入操作外,十字链表还支持删除操作、修改操作和查找操作。
这些操作都可以通过指针操作实现。
总的来说,十字链表是一种高效的数据结构,可以有效地存储稀
疏矩阵。
通过使用十字链表,我们可以对矩阵进行各种操作,而不必
费力遍历整个矩阵。
在实现稀疏矩阵算法时,我们常常会使用十字链
表这种数据结构。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
基于十字链表与三元组表的稀疏矩阵压缩存储实例研究作者:周张兰来源:《软件导刊》2017年第11期摘要:十字链表和带行链接信息的三元组表是稀疏矩阵的两种压缩存储方法。
十字链表为链式存储结构,带行链接信息的三元组表为顺序存储结构。
在MovieLens数据集上设计了分别采用十字链表和带行链接信息的三元组表对以用户为行、项目为列、用户评分为矩阵元的稀疏矩阵进行压缩存储,并在这两种存储结构上实现用户相似度计算算法。
通过测试分析和比较了两种不同的压缩存储方法在创建及相似度计算上的执行效率,并探讨了各自的特点及适用条件。
关键词关键词:稀疏矩阵;十字链表;三元组表;压缩存储DOIDOI:10.11907/rjdk.171845中图分类号:TP302文献标识码:A文章编号文章编号:16727800(2017)0110022040引言矩阵是科学与工程计算问题中研究的数学对象。
在高阶矩阵中,可能存在很多相同值或零值的矩阵元,对这些矩阵元的存储造成存储空间的浪费。
因此,可以对矩阵进行压缩存储,以节省存储空间,达到提高存储利用率的目的。
在算法实现中,选择的存储结构不同,执行效率也将不同。
对不同矩阵存储方法的特点进行分析和比较,有助于根据不同的实际应用,有针对性地选择更为合适的存储结构,以此提高矩阵运算及其它相关操作的运行效率。
1稀疏矩阵及存储若一个m行n列矩阵中的零元素有t个,零元素个数t与矩阵元总数m×n的比值称为稀疏因子,一般认为若稀疏因子不大于0.05,则此矩阵为稀疏矩阵。
设矩阵有10行10列,即总共100个元素,若其中零元素有95个,而非零元素仅有5个,则此矩阵为稀疏矩阵。
在存储稀疏矩阵时,可以采用非压缩存储和压缩存储两种方式。
非压缩存储使用二维数组,比如,设10行10列的稀疏矩阵M的矩阵元均为整数,则可以使用二维数组存储该矩阵M,数组的定义用C语言[1]描述如下:int a[10][10];其中,a[0][0]代表稀疏矩阵M第1行第1列元素,a[i][j]代表第i+1行第j+1列元素。
由于数组是从下标0开始,因此使用二维数组时下标与逻辑上的行号和列号相差1。
为了操作方便,也可以从a[1][1]开始存储第1行第1列元素,即定义为:int a[11][11];这种定义方式可以使逻辑上的行、列号与二维数组的下标保持一致,采用这种方式存储矩阵时所需存储空间要求更大。
从操作上看,二维数组的非压缩存储方式按行和列存取数据元素非常方便。
然而,在稀疏矩阵中,大量零元素的存储不仅会浪费存储空间,而且与零元素相关的运算也会在一定程度上降低计算总体效率。
因此,在实际应用中,对稀疏矩阵进行压缩存储很有必要。
压缩存储只存储非零矩阵元,有顺序存储和非顺序存储两种。
其中,顺序存储可使用带行链接信息的三元组表,而非顺序存储可采用十字链表。
2压缩存储2.1三元组及表示压缩存储只存储矩阵中的非零元。
在矩阵中,除了存储非零元素的值,还要存储该元素所在的行号和列号。
三元组表示法以(行号,列号,值)形式存储矩阵中的一个非零元素,比如,(1,1,5)表示第1行第1列元素值为5。
设矩阵元素值的类型为ElemType,则存储一个非零元三元组的结构体类型定义用C语言描述如下:struct Triple{int i,j;ElemType e;};三元组可存储稀疏矩阵中所有非零元的值及其行、列号。
但是,从三元组中不能得到整个稀疏矩阵行数、列数及非零元个数等信息。
因此,还要对存储整个稀疏矩阵的三元组表进行定义。
带行链接信息的三元组表又称为行逻辑链接顺序表[2],其结构体类型定义用C语言描述如下:struct TripleMatrix{struct Triple *data;int *rops;int m,n,t;};其中,以三元组形式表示的非零元存储在数组data[]中,每行的第1个非零元在三元组表中的起始位置存放在数组rops[]中,两个数组的存储空间在初始化时分配。
m、n、t分别代表稀疏矩阵的行数、列数和非零元个数。
2.2十字链表及表示十字链表是稀疏矩阵的链式存储结构。
十字链表对矩阵的每一行建立一个链表,行链表的头指针存放在其对应行的一维数组中。
同样,为矩阵的每一列建立一个链表,列链表的头指针也存放在其对应列的一维数组中。
为了实现十字链表,首先将需要存储的非零元素封装成一个结点。
其中,结点包含5个域,除了行号、列号和值3个数据域外,还有两个指针域,分别指向该元素所在行及所在列的下一个非零元素。
设稀疏矩阵的数据元素类型为ElemType,则十字链表中存储非零元素结点的结构体类型定义如下:struct CrossLNode{int i,j;ElemType e;struct CrossLNode *rownext, *colnext;};其中,rownext和colnext指针分别指向该元素所在行和所在列的下一个非零元素。
对整个稀疏矩阵而言,除了存储非零元,还要记录整个矩阵的行数、列数和非零元个数以及两个分别存储所有行链表和列链表头指针的一维数组。
因此,十字链表的结构体类型定义如下:struct CrossLinkList{struct CrossLNode *rowhead, *columnhead;int m, n, t;};在十字链表定义中,m、n、t分别代表稀疏矩阵的行数、列数和非零元素个数。
rowhead 和columnhead指针分别指向存储所有行链表头指针和所有列链表头指针的一维数组的首地址。
3实例测试3.1数据集协同过滤推荐[36]是一种广泛使用的推荐方法,传统的协同过滤推荐算法不依赖于推荐内容本身,而根据用户对项目的评分产生推荐。
其中,用户对项目的评分可以看作是一个以用户为行、项目为列的矩阵,矩阵元aij表示用户i对项目j的评分。
在实际应用中,用户有兴趣并且产生评分的项目相对于所有项目而言极其有限。
因此,用户项目评分矩阵往往是一个稀疏矩阵。
借助协同过滤推荐中常用的MovieLens[7]数据集作为数据对象,通过计算用户相似度,分析、对比分别采用带行链接信息的三元组表和十字链表对稀疏矩阵进行压缩存储时各自的执行效率。
本文使用的MovieLens数据集中,用户数943个、项目数1 682个。
用户评分记录包括:用户号、项目号、评分和时间戳。
其中,训练集有u1.base、u2.base、u3.base、u4.base和u5.base 5个数据文件,每个文件中评分记录为80 000条,每一条评分记录以(user_id,item_id,rating,timestamp)的形式存储。
分析此数据可以得出,以用户对项目评分为矩阵元的矩阵稀疏因子为80 000/(943×1 682)≈0.05,即此矩阵可认为是一个稀疏矩阵。
3.2算法设计在基于用户的协同过滤推荐中,首先利用评分矩阵计算用户与用户之间的相似度,从而产生目标用户的近邻,再利用这些用户近邻对目标用户未评分项目进行评分预测,最后根据预测评分的高低向目标用户推荐。
用户相似度计算是基于用户的协同过滤推荐算法的重要步骤,本文采用余弦相似度计算用户之间的相似度值。
为了比较带行链接信息的三元组表和十字链表压缩存储效率,分别在这两种存储结构上设计和实现用户相似度计算算法并进行了测试。
(1)带行链接信息的三元组表测试算法。
采用带行链接信息的三元组表压缩存储评分矩阵并计算用户相似度的算法步骤如下:首先,初始化TripleMatrix类型的带行链接信息的三元组表M;然后,创建带行链接信息的三元组表M。
读入评分记录文件u1.base,将其中每一条评分记录的行号、列号和值依次存储到M.data中;再计算用户之间的相似度,根据余弦相似度计算公式利用行链接信息M.rpos在三元组表中读取所需用户评分,并计算有共同评分的用户之间的相似度值;最后,重复以上步骤分别测试u2.base、u3.base、u4.base和u5.base 4个数据文件。
(2)十字链表测试算法。
采用十字链表存储评分矩阵并计算用户相似度的算法步骤与带行链接信息的三元组表类似,但由于该方法采用链式存储结构,因此在算法实现上主要是指针操作。
首先,初始化并创建CrossLinkList类型的十字链表T;然后,读入评分记录文件u1.base,每读取一条记录,用malloc动态申请一个CrossLNode类型的节点,并存储相应的行、列和值,同时将该结点挂到其行所在的链表及其列所在的链表中;接着,计算用户之间的相似度,根据余弦相似度计算公式在创建的十字链表中读取所需节点的用户评分,并计算有共同评分的用户之间的相似度值;最后,重复以上步骤分别测试u2.base、u3.base、u4.base和u5.base 4个数据文件。
3.3运行测试在配置为处理器Inter(R) Core(TM) i54210,内存4G,64位操作系统的电脑上使用visual C++6.0编程并运行测试。
测试中,使用计时函数clock()计算运行时间。
由于在不同时刻运行程序,计算的运行时间会有所偏差,为此将每个数据文件连续运行5次,取平均值作为最终执行时间。
用训练数据集u1至u5分别测试两种存储结构。
其中,创建十字链表和带行链接信息的三元组表所需时间如图1所示,计算用户相似度的运行时间如图2所示,创建并计算总执行时间如图3所示。
3.4结果分析由图1可知,创建十字链表所用时间大于带行链接信息的三元组表用时。
分析十字链表创建过程可知,每读入一条评分记录,动态申请一个结点的存储单元,不仅要将评分存储到结点中,还需要将该节点插入到其所在行和所在列的链表中。
相对于直接加到表尾的三元组表存储的顺序存储而言所需时间要多。
当然,由于在MovieLens数据集中,所有评分记录已按行和列从小到大顺序排序,因此当结点插入时只需要插入到所在行和列链表的表尾即可。
但是若输入的评分记录没有按行和列排序,在创建十字链表时还需要从行或列的第一个结点开始查找合适的插入位置,再实现节点插入。
这样所需运行时间更多,实际测试发现,其平均运行时间要多近3倍。
若采用三元组表存储,在创建时需对记录进行按行和列排序,插入节点时还要为空出插入位置而移动元素,则所需创建时间会更多。
当非零元素分别以十字链表和三元组表存储,采用余弦相似度计算方法计算所有用户之间的相似度。
其中,三元组表采用行逻辑连接的顺序表,可按行访问。
从图2中得到,使用十字链表计算相似度所需时间小于采用三元组表存储用时。
这说明,当十字链表和带行链接信息的三元组表创建后,在同等条件下计算用户相似度时,十字链表访问数据元素并实现计算的效率要比三元组表高。
从总运行时间看,十字链表虽然在创建用时比三元组表要多,但由于计算相似度所用时间少,因此总执行时间要比三元组表少,两者总运行时间对比如图3所示。