矩阵
- 1.特殊矩阵的压缩存储
- 1.1对称矩阵
- 1.2.三角矩阵
- 1.2.1.下三角矩阵
- 1.2.2. 上三角矩阵
- 1.3 带状矩阵
- 2.稀疏矩阵
- 2.1. 稀疏矩阵的三元组表存储
- 2.1.1. 稀疏矩阵的转置
1.特殊矩阵的压缩存储
矩阵与二维数组具有很好的对应关系,因此在进行科学计算时,常常用二维数组存储数学上的矩阵。但是在实际问题中,从数学模型中抽象出来的矩阵是一种特殊矩阵,如三角矩阵、对称矩阵、带状矩阵和稀疏矩阵,必然造成空间的浪费。本节从节约存储空间的角度考虑,研究这些特殊矩阵的存储方式。
1.1对称矩阵
对称矩阵的特点是:在一个n阶方阵中,有aij=aji,其中0 <= i,j <= n-1。由于对称矩阵关于主对角线对称,因此只需存储上三角或下三角部分即可。比如,只存储下三角中的元素aij,其特点是j<=i且0<=i<n-1;对于上三角中的元素aij,它和对应的aji相等。因此当访问的元素位于上三角时,直接去访问和它对应的下三角元素即可。这样,原来需要n * n个存储单元,现在只需要n(n+1)/2个存储单元,当n较大时,就可以可省相当可观的一部分存储资源。
对于任意的n阶对称矩阵,要存储其下三角部分,通常的方法是将下三角部分的所有元素以行为主序顺序存储到一个一维数组中去。在下三角中共有n(n+1)/2个元素,存储到一维数组sa[n(n+1)/2]中。此时ij对应数组的关系是怎么样呢?
对于下三角中元素aij,其特点是:j<=i且0<=i<=n-1。存储到sa中,根据存储原则,它前面有i行,共有1+2+……+i=i *(i+1)/2,因此它在sa中的下标k与i,j的关系如下:
若i<j,则aij是上三角中的元素,因为aij=aji。这样,访问上三角中aij时直接访问和它对应的下三角中的aji即可,在sa中的对应位置如下:
综上所述,对于对称矩阵中的任意元素aij,若令i=max(i,j),j=min(i,j),则将上两个式子综合起来即可得到:k=i * (i+1)/2 +j。
//创建对称矩阵
void symmetry_matrix(int A[], int n)
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j <= i; j++)
{
printf("请输入第%d行的%d个元素\n", i + 1, j + 1);
scanf("%d", &A[i * (i + 1) / 2 + j]);
}
}
}
//显示对称矩阵
void disp_symmetry_matrix(int A[], int n)
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (j <= i)
{
printf("%5d", A[i * (i + 1) / 2 + j]);
}
else
{
printf("%5d", A[j * (j + 1) / 2 + i]);
}
printf("\n");
}
}
}
1.2.三角矩阵
三角矩阵的特点是上三角或下三角是一个同常量c,下面讨论它们的压缩存储方法。
1.2.1.下三角矩阵
与对称矩阵类似,不同之处在于存完下三角中的所有元素之后,接着存储对角上方的常量c。因为是同一个常数,所有存储一个即可,这样一共需要存储
n(n+1)/2+1个元素。设存入一维数组sa[n(n+1)/2+1]时,这种存储方式可节约n(n-1)/2-1个存储单元。
sa[k]与a[i][j]的对应关系为:
1.2.2. 上三角矩阵
对于上三角矩阵,以行为主序存储上三角部分,最后存储对角线下方的常量c,
和下三角的i和j方向相反。
1.3 带状矩阵
对于n阶矩阵A,如果存在最小正数m,满足当|i-j|>=m时,aij=0,则称A为带状矩阵并且称w=2m-1为矩阵A的带宽。其特点是所有非零元素都集中在以主对角线为中心的带状区域中,除了主对角线和它的上下方若干条对角线的元素外,所有其他元素都为零。
带状矩阵A的压缩存储到一个n行w列的二维数组b中。那么A[i][j]映射为B[m][n],映射关系为:m=i,n=j-i+1 。
2.稀疏矩阵
设m * n矩阵中非零元素的分布没有规律,且非零元素个数为t(t<< m * n),则称该矩阵为稀疏矩阵。一种有效的解决方案是:对于每一个非零元素,不仅存储元素值,还存储它所在的行和列,即将非零元素所在的行、列及值构成一个三元组(i,j,v),然后再按某种规律存储这些三元组。
2.1. 稀疏矩阵的三元组表存储
将稀疏矩阵中的三元组按行优先的顺序以及同一行中列号从小到大的规律排列成一个线性表,称为三元组表。
显然,要唯一低表示一个稀疏矩阵,在存储空间三元组表的同时还需要存储行数和列数。此外,为了运算方便,还要存储矩阵的非零元素的个数。
typedef struct SPNode
{
int i, j; //非零元素的行列
DataType v; //非零元素值
}SPNode; //三元数组类型
typedef struct SPMatrix
{
SPNode data[SMAX]; //三元组表
int mu, nu, tu; //矩阵的行、列及非零元素的个数
}SPMatrix; //三元组表的存储类型
这样的存储方法极大地压缩了稀疏矩阵的存储空间,但是会使矩阵的运算变得复杂。下面讨论基于三元组存储方式的稀疏矩阵的两种运算:转置和相乘。
2.1.1. 稀疏矩阵的转置
设:SPMatrix TA,TB;
其中,TA表示一个m * n的稀疏矩阵对应存储的三元组表,TB对应的是其转置矩阵n * m的稀疏矩阵对应存储的三元组表。
根据转置算法可知,由TA求TB,需要进行如下操作:
(1)将TA的行、列转化成TB的列、行。
(2)将TA.data中每一个三元组的行列变换后存储到TB.data中.
需要注意的是,无论是TA还是TB,都以行优先进行存储 。如果依次取出TA中的每个三元组,转换为TB中的对应三元组,则无法确定TA.data[k]在TB中的存储位置。为此,按列序从TA中依次取出每一个三元组,并将其转换,便可对应于TB中按行存储的三元组。
void TransM1(SPMatrix TA, SPMatrix* TB)
{
int p, q, col;
//稀疏矩阵的行、列和元素个数
TB->mu = TA.nu;
TB->nu = TA.mu;
TB->tu = TA.tu;
if (TB->tu > 0) //有非零元素则转换
{
q = 0;
for (col = 0; col < (TA.nu); col++)
{
for (p = 0; p < (TA.tu); p++)//扫描整个三元组表
{
if (TA.data[p].j == col)
{
TB->data[q].i = TA.data[p].j;
TB->data[q].j = TA.data[p].i;
TB->data[q].v = TA.data[p].v;
q++;
}
}
}
}
}
【算法分析】
该算法的时间主要耗费在col和p的二重循环上,所以时间复杂度为O(n * t)(设m、n是原矩阵的行、列,t是稀疏矩阵的非零元素个数),显然当非零元素的个数t和 m * n数量极同时,算法的时间复杂度为O(m * n * n),和通常存储方式的矩阵装置算法相比,可能节约了一定量的存储空间,但算法的时间性能更差一些。
算法一效率低的原因是,要从TA的三元组表中寻找第0列、第1列……需反复搜索TA表,若能直接确定TA中每一个三元组在TB中的位置,则对TA的三元表扫描一次即可。
void TransM2(SPMatrix TA, SPMatrix* TB)
{
int i, j, k;
int num[SMAX] = { 0 };
int cpot[SMAX] = { 0 };
//稀疏矩阵的行、列和元素个数
TB->mu = TA.nu;
TB->nu = TA.mu;
TB->tu = TA.tu;
if (TB->tu > 0)
{
for (i = 0; i < TA.tu; i++)
{
j = TA.data[i].j;
num[j]++;
}
for (i = 1; i < TA.nu; i++)
{
cpot[i] = cpot[i - 1] + num[i - 1];
}
for (i = 0; i < TA.tu; i++)
{
j = TA.data[i].j;
k = cpot[j];
TB->data[k].i = TA.data[i].j;
TB->data[k].j = TA.data[i].i;
TB->data[k].v = TA.data[i].v;
cpot[j]++;
}
}
}