28、数据结构笔记之二十八数组之矩阵
“在命运的颠沛中,最可以看出人们的气节。 -- 莎士比亚”
上篇咱们看了数组的一些定义和概念,接下去我们来看下数据和矩阵。
二维数组可以来存储矩阵元素。有的程序设计语言中还供了各种矩阵运算,给用户使用带来很大的方便。然而,在数值分析中经常出现一些高阶矩阵,在这些高阶矩阵中有许多值相同的元素或者是零元素,为了节省存储空间对这类矩阵采用多个值 相同的元素只分配一个存储空间,有时零元素不存储的存储策略,称为矩阵的压缩存储。
如果值相同的元素或者零元素在矩阵中的分布有一定规律,称此类矩阵为特殊矩阵,反之称为稀疏矩阵。
若一个n阶矩阵的主满足aij=aj i 1≤i,j≤n,则称该矩阵为n阶对称矩阵。
由于对称矩阵有上述性质,可以为每一对对称元分配一个存储空间,这样就将n2个元的存储空间压缩成n(n+1)/2个单元的存储空间,以行序为主序存储对称矩阵的下三角(包括对角线)的元素。
三角矩阵分上三角矩阵和下三角矩阵两种。上三角矩阵的对角线左下方的系数全部为零,下三角矩阵的对角线右上方的系数全部为零。三角矩阵可以看做是一般方阵的一种简化情形。
对角矩阵(diagonalmatrix)是一个主对角线之外的元素皆为 0 的矩阵。对角线上的元素可以为 0或其他值。
矩阵中非零元素的个数远远小于矩阵元素的总数,并且非零元素的分布没有规律,则称该矩阵为稀疏矩阵(sparsematrix);与之相区别的是,如果非零元素的分布存在规律(如上三角矩阵、下三角矩阵、对称矩阵),则称该矩阵为特殊矩阵。
人们无法给出确切的定义,它只是一个凭人们的直觉来了解的概念。假设在m×n的矩阵中,有t个非零元素,若t远远小于矩阵元素的总数(即t< 精确点,设在矩阵A中,有t个非零元素。令,称δ为矩阵的稀疏因子。通常认为δ≤0.05时称之为稀疏矩阵。 在存储稀疏矩阵时,为了节省存储单元,很自然地想到使用压缩存储方法。 但由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置(i,j)。反之,一个三元组(i,j,aij)唯一确定了矩阵A的一个非零元。因此,稀疏矩阵可由表示非零元的三元组及其行列数唯一确定。比如(1,2,3)和(3,2,1)就是两个不同的有序三元组. 方法在三元组顺序表的基础上,增加一个数组存储矩阵中每行第一个非0元素出现的位置。 十字链表表示稀疏矩阵比较复杂。具体查看《18、数据结构笔记之十八链表实现稀疏矩阵》 主要考虑的是对称矩阵,三角矩阵及三对角矩阵的一些操作。 定义了如下全局变量 //矩阵的压缩存储 int TA[MaxN],TC[MaxN]; int n; int A[MaxN][MaxN],B[MaxN][MaxN]; 输入要操作的对称矩阵阶数。 输入每个矩阵中的每个数。只接受一半的数字输入即可。 然后通过循环实现另一半的数字录入。 然后输出矩阵。 最后将矩阵输入到一个数组中,并进行输出。 void value() { inti,j,k; printf("请输入要操作的对称矩阵的阶数:"); scanf("%d",&n); printf("输入对称矩阵:"); for(i=1;i<=n;i++) for(j=i;j<=n;j++) scanf("%d",&A[i][j]); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i>j) A[i][j]=A[j][i]; else A[i][j]=A[i][j]; } } printf("输出对称矩阵:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) TA[k++]=A[i][j]; printf("压缩后的对称矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); printf("\n"); } 输入上三角矩阵的阶数,然后输入数字,只取行数大于列数的数字。其他值为0. 然后进行输出,最后复制给数组进行输出。 void sfdg() { inti,j,k; printf("请输入要操作的上三角矩阵的阶数:"); scanf("%d",&n); printf("输入上三角矩阵:"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i>j) A[i][j]=0; else scanf("%d",&A[i][j]); } } printf("输出上三角矩阵:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) TA[k++]=A[i][j]; printf("压缩后的上三角矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); printf("\n"); } 下三角同理。 输入三对角矩阵的阶数。 存储3条主对角线上的数字,然后保存输出。 void store() { inti,j,k; printf("请输入要操作的三对角矩阵的阶数:"); scanf("%d",&n); printf("输入三对角矩阵:"); for(i=1;i<=n;i++) { if(i==1) { for(j=1;j<=2;j++) scanf("%d",&A[i][j]); } elseif(i>1&&i { for(j=i-1;j<=i+1;j++) scanf("%d",&A[i][j]); } elseif(i=n) { for(j=i-1;j<=i;j++) scanf("%d",&A[i][j]); } } printf("输出三对角矩阵:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) if(A[i][j]!=0) TA[k++]=A[i][j]; printf("压缩后的三对角矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); } 输入三对角矩阵阶数,先输入A 然后输入矩阵B。 只是个菜单,详见源码部分。 Main函数实现主菜单函数的调用,然后根据输入数字调用不同的函数。 如下图1: #include #include #defineMaxN100 //矩阵的压缩存储 int TA[MaxN],TC[MaxN]; int n; int A[MaxN][MaxN],B[MaxN][MaxN]; void value() { inti,j,k; printf("请输入要操作的对称矩阵的阶数:"); scanf("%d",&n); printf("输入对称矩阵:"); for(i=1;i<=n;i++) for(j=i;j<=n;j++) scanf("%d",&A[i][j]); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i>j) A[i][j]=A[j][i]; else A[i][j]=A[i][j]; } } printf("输出对称矩阵:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) TA[k++]=A[i][j]; printf("压缩后的对称矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); printf("\n"); } void sfdg() { inti,j,k; printf("请输入要操作的上三角矩阵的阶数:"); scanf("%d",&n); printf("输入上三角矩阵:"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i>j) A[i][j]=0; else scanf("%d",&A[i][j]); } } printf("输出上三角矩阵:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) TA[k++]=A[i][j]; printf("压缩后的上三角矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); printf("\n"); } void sfvd() { inti,j,k; printf("请输入要操作的下三角矩阵的阶数:"); scanf("%d",&n); printf("输入下三角矩阵:"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i>=j) scanf("%d",&A[i][j]); else A[i][j]=0; } } printf("输出下三角矩阵:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) TA[k++]=A[i][j]; printf("压缩后的下三角矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); printf("\n"); } void store() { inti,j,k; printf("请输入要操作的三对角矩阵的阶数:"); scanf("%d",&n); printf("输入三对角矩阵:"); for(i=1;i<=n;i++) { if(i==1) { for(j=1;j<=2;j++) scanf("%d",&A[i][j]); } elseif(i>1&&i { for(j=i-1;j<=i+1;j++) scanf("%d",&A[i][j]); } elseif(i=n) { for(j=i-1;j<=i;j++) scanf("%d",&A[i][j]); } } printf("输出三对角矩阵:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) if(A[i][j]!=0) TA[k++]=A[i][j]; printf("压缩后的三对角矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); } void add() { inti,j,k; printf("请输入要操作的三对角矩阵的阶数:"); scanf("%d",&n); printf("输入三对角矩阵A:"); for(i=1;i<=n;i++) { if(i==1) { for(j=1;j<=2;j++) scanf("%d",&A[i][j]); } elseif(i>1&&i { for(j=i-1;j<=i+1;j++) scanf("%d",&A[i][j]); } elseif(i=n) { for(j=i-1;j<=i;j++) scanf("%d",&A[i][j]); } } printf("输出三对角矩阵A:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",A[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) if(A[i][j]!=0) TA[k++]=A[i][j]; printf("压缩后的三对角矩阵A:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TA[k]); printf("\n"); printf("输入三对角矩阵B:"); for(i=1;i<=n;i++) { if(i==1) { for(j=1;j<=2;j++) scanf("%d",&B[i][j]); } elseif(i>1&&i { for(j=i-1;j<=i+1;j++) scanf("%d",&B[i][j]); } elseif(i=n) { for(j=i-1;j<=i;j++) scanf("%d",&B[i][j]); } } printf("输出三对角矩阵B:\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%-4d",B[i][j]); printf("\n"); } k=0; for(i=1;i<=n;i++) for(j=1;j<=n;j++) if(B[i][j]!=0) TC[k++]=B[i][j]; printf("压缩后的三对角矩阵B:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TC[k]); intTB[MaxN]; for(k=0;k<=n*n-1;k++) TB[k]=TA[k]+TC[k]; printf("相加后的压缩三对角矩阵:\n"); for(k=0;k<=n*n-1;k++) printf("%-4d",TB[k]); printf("\n"); } //主菜单 int menu_list() { int c; printf("\n\n**************************特殊矩阵的压缩存储**************************\n\n"); printf(" 1.对称矩阵的压缩存储\n"); printf(" 2.上三角矩阵的压缩存储\n"); printf(" 3.下三角矩阵的压缩存储\n"); printf(" 4.三对角矩阵的压缩存储\n"); printf(" 5.三对角矩阵的加法运算\n"); printf(" 6.退出系统\n"); printf(" 请输入(1-6)[ ]\b\b"); while(1) { scanf("%d",&c); if(c<1||c>6) printf("输入错误,请重新输入:"); else break; } return c; } //主函数 void main() { while(1) { switch(menu_list()) { case 1: value(); break; case 2: sfdg(); break; case 3: sfvd(); break; case 4: store(); break; case 5: add(); break; case 6: printf(" 程序结束,谢谢您的使用!\n\n"); exit(0); } } } //定义结构体 #defineMAXSIZE100 typedefstruct { inti,j; intdat; }Triple; //矩阵的顺序链表 typedefstruct { Tripledata[MAXSIZE+1]; intmu,nu,tu; }TSMatrix; 输入两个参数M,和T的指针。 通过M.nu创建内存空间,分别给num和cpot。 将M的值复制给T矩阵。 Num是每列非零元素个数的数组。 Cpot是每列第一个非零元素位置 //稀疏矩阵存储 int FastConvert(TSMatrixM,TSMatrix *T) { int*num,*cpot,i,p,col; //分别为一维指针分配内存 num=(int*)malloc(sizeof(int)*M.nu); cpot=(int*)malloc(sizeof(int)*M.nu); T->mu=M.nu;T->nu=M.mu;T->tu=M.tu; if(T->tu) { //初始化每一列非零元素个数为零 for(i=0;i<M.nu;i++) num[i]=0; //初始化数组将其置为每一列的非零元素个数 for(i=0;i<M.tu;i++) ++num[M.data[i].j]; //cpot数组表示每一列第一个非零元素位置 cpot[0]=0; for(i=1;i<M.nu;i++) cpot[i]=cpot[i-1]+num[i-1]; //进行矩阵值的交换 for(i=0;i<M.tu;i++) { col=M.data[i].j; p=cpot[col]; T->data[p].i=M.data[i].j; T->data[p].j=M.data[i].i; T->data[p].dat=M.data[i].dat; ++cpot[col]; } } return1; } 定义变量T,M。定义矩阵指针为NULL。输入行和列数量。 然后创建行数乘以列数的的整型指针数量。然后输入数据。 如果为0,则不增加变量tu和count的值。然后调用FastConvert函数。 运行如下图2 所示: 可以看到最后将一个稀疏矩阵成功的转化成为了一个数组。 不过需要注意的是: 稀疏矩阵压缩存储后,必会失去随机存取功能。 int main(intargc,char* argv[]) { TSMatrixT,M; intcol,row,i,j,count=0; int**p=NULL; printf("请输入矩阵的行列row 和 col\n"); scanf("%d%d",&row,&col); //为未知的二维指针分配内存 p=(int**)malloc(sizeof(int*)*row); for(i=0;i p[i]=(int*)malloc(sizeof(int)*col); printf("请输入数据!\n"); M.mu=row; M.nu=col; M.tu=0; for(i=0;i for(j=0;j { scanf("%d",&p[i][j]); if(p[i][j]!=0) { M.data[count].dat=p[i][j]; M.data[count].i=i; M.data[count].j=j; M.tu++; count++; } } count=0; FastConvert(M,&T); for(i=0;i { printf("%d ",T.data[count].dat); count++; } free(p); return0; } #include #include //定义结构体 #defineMAXSIZE100 typedefstruct { inti,j; intdat; }Triple; //矩阵的顺序链表 typedefstruct { Tripledata[MAXSIZE+1]; intmu,nu,tu; }TSMatrix; int FastConvert(TSMatrixM,TSMatrix *T); int main(intargc,char* argv[]) { TSMatrixT,M; intcol,row,i,j,count=0; int**p=NULL; printf("请输入矩阵的行列row 和 col\n"); scanf("%d%d",&row,&col); //为未知的二维指针分配内存 p=(int**)malloc(sizeof(int*)*row); for(i=0;i p[i]=(int*)malloc(sizeof(int)*col); printf("请输入数据!\n"); M.mu=row; M.nu=col; M.tu=0; for(i=0;i for(j=0;j { scanf("%d",&p[i][j]); if(p[i][j]!=0) { M.data[count].dat=p[i][j]; M.data[count].i=i; M.data[count].j=j; M.tu++; count++; } } count=0; FastConvert(M,&T); for(i=0;i { printf("%d ",T.data[count].dat); count++; } free(p); return0; } //稀疏矩阵存储 int FastConvert(TSMatrixM,TSMatrix *T) { int*num,*cpot,i,p,col; //分别为一维指针分配内存 num=(int*)malloc(sizeof(int)*M.nu); cpot=(int*)malloc(sizeof(int)*M.nu); T->mu=M.nu;T->nu=M.mu;T->tu=M.tu; if(T->tu) { //初始化每一列非零元素个数为零 for(i=0;i<M.nu;i++) num[i]=0; //初始化数组将其置为每一列的非零元素个数 for(i=0;i<M.tu;i++) ++num[M.data[i].j]; //cpot数组表示每一列第一个非零元素位置 cpot[0]=0; for(i=1;i<M.nu;i++) cpot[i]=cpot[i-1]+num[i-1]; //进行矩阵值的交换 for(i=0;i<M.tu;i++) { col=M.data[i].j; p=cpot[col]; T->data[p].i=M.data[i].j; T->data[p].j=M.data[i].i; T->data[p].dat=M.data[i].dat; ++cpot[col]; } } return1; } 2.1 稀疏矩阵存储
2.1.1 三元组
2.1.2 行逻辑链接顺序表
2.1.3 十字链表
3. 特殊矩阵压缩存储
3.1 定义
3.2 Value
3.3 Sfdg
3.4 Sfvd
3.5 Store
3.6 Add
3.7 menu_list
3.8 Main函数
3.9 源码
4. 稀疏矩阵压缩存储
4.1 定义
4.2 FastConvert
4.3 Main
稀疏矩阵在采用压缩存储后将会失去随机存储的功能。因为在这种矩阵中,非零元素的分布是没有规律的,为了压缩存储,就将每一个非零元素的值和它所在的行、列号做为一个结点存放在一起,这样的结点组成的线性表中叫三元组表,它已不是简单的向量,所以无法用下标直接存取矩阵中的元素。4.4 源码