数据结构重读 - 矩阵的压缩和存储

矩阵在数值运算中很常见,本节关注如何存储矩阵的元,从而使矩阵的各种运算能有效进行。

如果矩阵中有许多相同值的元素或者很多零元素。有时为了节省存储空间,可以对这类矩阵进行存储压缩,称为稀疏矩阵。更进一步的,如果稀疏矩阵的相同值或零元素分布还是有规律的,我们可以称他们为特殊矩阵

对称矩阵

例如:

1 2 4
2 3 5
4 5 6

我们可以为每一对称元分配一个存储空间,即可以将n^2个元压缩存储到n*(n+1)/2个空间中。

假设在线性(一元)数组中存储,下标是k。m[i][j]是矩阵中第i行第j列的元素,则:

当i>=j时,a[k] = m[i][j],k=i*(i-1)/2 + j -1

当i<j时,a[k] = m[i][j],k = j*(j-1)/2 +i -1

稀疏矩阵

设在m*n的矩阵中,有t个元素非零,则a=t/(m*n)称为稀疏因子,当a<=0.05,即只有不到5%的元素非零时,称为稀疏矩阵

在很多问题中,实际都是稀疏矩阵。

压缩存储可以只存储非零元,例如用一个三元组表示:(i, j, val),i和j是行、列,val是矩阵位置的元素值。

注意:在如下三元组存储中,data是从下标0开始的。但是矩阵的行、列是从1开始的!

下面是数据结构定义、初始化、打印等操作:

#include <stdio.h>
#include <memory.h>

#define MAX 1024

struct Triple
{
	int i,j; // row and column
	int value;
};

struct TMatrix
{
	struct Triple data[MAX]; // Triple
	int ndata; // number of data
	int rows; // row nums
	int cols; // col nums
};

void tmatrix_init(struct TMatrix* matrix)
{
	matrix->ndata = matrix->rows = matrix->cols = 0;
}

void tmatrix_print(struct TMatrix* matrix)
{
	int i,j;
	int k = 0;
	for(i=1;i<=matrix->rows;i++)
	{
		for(j=1;j<=matrix->cols;j++)
		{
			if(k<matrix->ndata && matrix->data[k].i==i && matrix->data[k].j==j)
			{
				printf("%3d ", matrix->data[k].value);
				k++;
			}else
			{
				printf("%3d ", 0);
			}
		}
		printf("\n");
	}
}

int main()
{
	// Init matrix m
	struct TMatrix m, t;
	tmatrix_init(&m);
	// Set m
	m.rows = 6;
	m.cols = 7;
	m.ndata = 8;
	m.data[0].i = 1;
	m.data[0].j = 2;
	m.data[0].value = 12;
	m.data[1].i = 1;
	m.data[1].j = 3;
	m.data[1].value = 9;
	m.data[2].i = 3;
	m.data[2].j = 1;
	m.data[2].value = -3;
	m.data[3].i = 3;
	m.data[3].j = 6;
	m.data[3].value = 14;
	m.data[4].i = 4;
	m.data[4].j = 3;
	m.data[4].value = 24;
	m.data[5].i = 5;
	m.data[5].j = 2;
	m.data[5].value = 18;
	m.data[6].i = 6;
	m.data[6].j = 1;
	m.data[6].value = 15;
	m.data[7].i = 6;
	m.data[7].j = 4;
	m.data[7].value = -7;
	// Fast Trans
	tmatrix_fast_transpose(&m, &t);
	// Print m
	//tmatrix_print(&m);
	tmatrix_print(&t);
	return 0;
}

快速转置算法

这样的存储虽然省了不少空间,但是在一些操作如转置时算法复杂度会上升很多。因为你无法再随机访问m[i][j]了。针对这种情况,有了如下的快速转置算法。它使用两个辅助向量,nrows和frows。这里的rows都是针对转置结果矩阵T而言的。(我们假设从矩阵M转置称为矩阵T)

首先计算nrows,它表示矩阵T中每一行有多少个非空元素即三元组需要存储。实际上也就是M中列j为对应nrows下标的。于是我们扫描一遍M.data的所有元素,对应j在nrows下标中++即可。

然后计算frows,就是T矩阵每一行第一个元素应该位于T的data的哪个位置。初始的第一个frows[1] = 0。这个需要解释下,因为矩阵行列开始都是1,所以frows里面是1,但是data存储是从0开始,所以起始是0。

最后就是快速转置算法,利用上面两个向量就很简单了,直接上代码:

// Transpose m to t
void tmatrix_fast_transpose(struct TMatrix* m, struct TMatrix* t)
{
	// Vars
	int nrows[MAX]; // Number of triple in new matrix t each row(t)
	int frows[MAX]; // First position in new matrix t each row(t)
	int i,j;
	// Basic dat
	t->rows = m->cols;
	t->cols = m->rows;
	t->ndata = m->ndata;
	// Calculate nrows(t), num of data same col in m
	memset(nrows, 0, MAX*sizeof(int));
	j=0;
	for(i=0;i<m->ndata;i++)
	{
		nrows[m->data[i].j]++;
	}
	// Calculate frows
	memset(frows, 0, MAX*sizeof(int));
	frows[1] = 0; // Matrix's row / col is 1/1, but in data, stores begin at [0]
	for(i=2;i<=m->ndata;i++)
	{
		frows[i] = frows[i-1] + nrows[i-1];
	}
	// Transpose
	for(i=0;i<m->ndata;i++)
	{
		t->data[frows[m->data[i].j]].j = m->data[i].i;
		t->data[frows[m->data[i].j]].i = m->data[i].j;
		t->data[frows[m->data[i].j]].value = m->data[i].value;
		frows[m->data[i].j]++;
	}

}

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *