您的位置:首页 > 其它

稀疏矩阵的压缩存储与转置与加法

2017-04-22 21:42 363 查看

稀疏矩阵

什么是稀疏矩阵:

稀疏矩阵:在M*N的矩阵中,有效元素的个数远小于无效元素的个数(一般是:有效/无效 < 0.05),且这些数据的分布没有规律。可能表述的不是很完美,所以稀疏矩阵>

例如:



从上图中我们可以看到,其中只有6个有效元素,用红色标记起来。

根据上面我们刚说过的特征,我们可以看到其中0特别多,而别的数据却很少,其实我们一般把0称为是无效元素,其他的称为有效元素,和我们在上个对称矩阵的压缩存储中所说,存储这么多无效元素干嘛,浪费内存(内存条那么贵是吧???),所以我们只需要存储有效元素就可以了。但是这个稀疏矩阵没有任何规律我们不能像对称矩阵那样存储,所以就要特别的存储办法。

稀疏矩阵的压缩存储

我们了解到稀疏矩阵后,现在考虑怎么存储稀疏矩阵中的有效元素,我们从图中可以看到,其中有效元素的分布是没有任何规律的(虽然我给的其实还是有点规律的),从稀疏矩阵的严格定义中来说是没有规律的,所以存储他们还是要借用一些别的力量,它存储的是有效元素(行坐标,列坐标,值大小),所以我们就能够想到用结构体来封装一个存储单元。

template<class T>
struct Trituple
{
Trituple(int row = 0, int col = 0, const T& value = T())
: _row(row)
, _col(col)
, _value(value)
{}

size_t _row;//有效元素的行
size_t _col;//有效元素的列
T _value;//有效元素的值
};


我们意见给出了它的存储结构,如何利用他来存储数据元素我们在下面的代码中实现:

SparseMatrix(T* array, size_t row, size_t col, const T& invalid)
: _row(row)
, _col(col)
, _invalid(invalid)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (array[i*col + j] != invalid)//判断是否为无效值,
{
Trituple<T> tmp(i, j, array[i*col + j]);//将有效值存放在三元组中
_sm.push_back(tmp); //借用vector来存储数据
}
}
}
}


稀疏矩阵的转置

上面我们已经介绍了稀疏矩阵,和稀疏矩阵的压缩存储,下面来说一下稀疏矩阵的转置,直接上图来看一下转置后的效果



从图中我们可以看到从6行5列变为了5行6列,其实就是将[i][j]与[j][i]位置上的数据进行了交换;一般我们在存储的时候选择的是行优先;

行优先:1 3 5 1 3 5(一行一行的存储)
列优先:1 1 3 3 5 5(一列一列的存储)


从上面行列优先的区别我们可看到的是,行优先存储完后它的新行上面的元素和列优先是一模一样的,所以我们可以在逆置的时候采用列优先从而达到显示出来的是行优先。(完美)

So:

普通转置

因为我们已经将矩阵的有效元素存储在_sm中了,所以我们需要一个新的_sm2来存储按照列优先排列的元素,所以:

第一列遍历(_sm2)中存储:1,1;
第二列遍历(_sm2)中存储:1,1;
第三列遍历(_sm2)中存储:1,1,3,3;
第四列遍历(_sm2)中存储:1,1,3,3;
第五列遍历(_sm2)中存储:1,1,3,3,5,5;


所以这就是新的压缩存储数组;

SparseMatrix<T> Transport()//时间复杂度 O(列数*有效元素的个数+有效元素的个数)
{
vector<Trituple<T>> _sm2;
size_t n = _sm.size();
_sm2.resize(n);
size_t count = 0;

for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < n; j++)
{
if (_sm[j]._col == i)   //把_sm中列为i的数找出来作为将来的行,以列优先达到复位是的行优先
{
_sm2[count] = _sm[j]; //将数据保存在_sm2中返回是就是按照行优先来计算的
swap(_sm2[count]._row, _sm2[count]._col);//交换坐标
count++;
}
}
}
for (size_t i = 0; i < n; i++)
{
_sm[i] = _sm2[i];//在operator中调用默认的_sm是第一个的,所以还是按照原来的有效值存储顺序打印的,只是将行列交换后,原来每个行列的大小不一样而已,没有改变实质值;
}
swap(_row, _col);
return *this;
}


快速转置

在快速转置中,我们是用空间上的占用换取时间上的节省(有没有觉得很熟悉???(为什么要内存对齐???))

我们需要两个数组

Count[]//统计转置后矩阵每一行的个数(原矩阵每一列的个数)
Strant[]//统计转置后的矩阵每行在矩阵压缩中存储的位置(统计原矩阵中每一列第一个元素在_sm2中存储的位置)


我们通过存储后_sm中存储的是:1 3 5 1 3 5;

第一次遍历遇到1的时候放在_sm2: 1, , , , , 。
第二次遍历遇到3的时候放在_sm2: 1, , 3, , , 。
第三次遍历遇到5的时候放在_sm2: 1, , 3, , 5, 。
第四次遍历遇到1的时候放在_sm2: 1,1 , 3, ,5 , 。
第五次遍历遇到3的时候放在_sm2: 1,1 , 3,3 ,5 , 。
第六次遍历遇到3的时候放在_sm2: 1, 1, 3,3 ,5 ,5 。


所以我们遍历一次就可以了。我们已经计算出了_sm2中的数据下面计算Count[]的数据;

因为在Count[]中我们存储的是原来_sm中每列上面的数据个数,因此直接计算它的统计列就可以;

在Strant[]中我们计算的是每一列第一个元素在_sm2中存储的位置,所以我们计算的时候,只需要用上一组的数据个数加上它的它的上一列开头的位置,才能计算出这一列的位置;



Count[] = 2, 0, 2, 0, 2;

Start
= Start[n-1]+Count[n-1];

所以很好计算的;就是不太好理解(我也是参考了好几个人的写法,才大概理解了这个写法);

参看代码:

SparseMatrix<T> FastTransport()
{
vector<Trituple<T>> _sm2;
size_t n = _sm.size();
_sm2.resize(n);

size_t Count[5] = {0};//统计转置后矩阵每一行的个数(原矩阵每一列的个数)
size_t Start[5] = {0};//统计转置后的矩阵每行在矩阵压缩中存储的位置(统计原矩阵中每一列第一个元素在_sm2中存储的位置)
for (size_t i = 0; i < n; i++)
{
Count[_sm[i]._col]++;  //因为在Count初始化的时候全部初始化为0,所以在Count[_sm[i]._col]中找到第几列的时候就是给第几列++;计算出每列的元素个数;
}
for (size_t i = 1; i < _col; i++)
{
Start[i] = Start[i - 1] + Count[i - 1];
}

for (size_t i = 0; i < n; i++)
{
_sm2[Start[_sm[i]._col]] = _sm[i];//利用Start+Count计算出_sm2中每个元素的位置;
swap(_sm2[Start[_sm[i]._col]]._col, _sm2[Start[_sm[i]._col]]._row);
Start[_sm[i]._col]++;
}

for (size_t i = 0; i < n; i++)
{
_sm[i] = _sm2[i];
}
swap(_row, _col);
return *this;
}




两个稀疏矩阵的加法

简单介绍一下两个稀疏矩阵的加法,我们在矩阵加法的时候都是,两个矩阵的行列相同进行相加,所以,我们在完成代码编写的时候,要对有效元素的左边进行一个判断:

SparseMatrix<T> operator+(const SparseMatrix<T>& sp)//两个矩阵的行列数相同
{
vector<Trituple<T>> _tmp;
size_t n1 = _sm.size();
size_t n2 = sp._sm.size();
_tmp.resize(n1 + n2);  //有点浪费空间

int i = 0, j = 0, k = 0;
while ((i < n1) && (j < n2))
{
if (_sm[i]._row == sp._sm[j]._row)//行相等
{
if (_sm[i]._col == sp._sm[j]._col)//列相等
{
_tmp[k]._row = _sm[i]._row;
_tmp[k]._col = _sm[i]._col;
_tmp[k]._value = _sm[i]._value + sp._sm[j]._value;
i++;
j++;
k++;
}
else if (_sm[i]._col < sp._sm[j]._col) //_sm列小于sp列 ,小的先放入_tmp中
{
_tmp[k]._row = _sm[i]._row;
_tmp[k]._col = _sm[i]._col;
_tmp[k]._value = _sm[i]._value;
i++;
k++;
}
else
{
_tmp[k]._row = sp._sm[j]._row;
_tmp[k]._col = sp._sm[j]._col;
_tmp[k]._value = sp._sm[j]._value;
j++;
k++;
}
}
else if (_sm[i]._row < sp._sm[i]._row)//_sm的行号小于sp,把这一行的数据都放进去
{
_tmp[k]._row = _sm[i]._row;
_tmp[k]._col = _sm[j]._col;
_tmp[k]._value = _sm[j]._value;
i++;
k++;
}
else
{
_tmp[k]._row = sp._sm[j]._row;
_tmp[k]._col = sp._sm[j]._col;
_tmp[k]._value = sp._sm[j]._value;
j++;
k++;
}
}

_sm.resize(k);
for (size_t i = 0; i < k; i++)
{
_sm[i] = _tmp[i];
}
return *this;
}


下面来展示上述所有问题的完整代码,供大家参考:

#include<iostream>
using namespace std;
#include <vector>
template<class T>

class SparseMatrix
{
template<class T>
struct Trituple
{
Trituple(int row = 0, int col = 0, const T& value = T())
: _row(row)
, _col(col)
, _value(value)
{}

size_t _row;//有效元素的行
size_t _col;//有效元素的列
T _value;//有效元素的值
};

public:
//
SparseMatrix(T* array, size_t row, size_t col, const T& invalid)
: _row(row)
, _col(col)
, _invalid(invalid)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (array[i*col + j] != invalid)//判断是否为无效值,
{
Trituple<T> tmp(i, j, array[i*col + j]);//将有效值存放在三元组中
_sm.push_back(tmp); //借用vector来存储数据
}
}
}
}

SparseMatrix()
: _row(0)
: _col(0)
, _invalid(0)
{}

// 访问矩阵中的元素
T& Acess(int row, int col)
{
size_t idx = 0;
for (size_t i = 0; i < _row; i++)
{
for (size_t j = 0; j < _col; j++)
{
if (idx < _sm.size() && _sm[idx]._row == row && _sm[idx]._col == col)
{
cout << _sm[idx]._value << endl;
return _sm[idx]._value;
}
}
}
cout << _invalid << endl;
return _invalid;
}
template<class T>
friend ostream& operator<<(ostream& _cout, const SparseMatrix<T>& sm)
{
size_t idx = 0;
for (size_t i = 0; i < sm._row; i++)
{
for (size_t j = 0; j < sm._col; j++)
{
if (idx < sm._sm.size() && sm._sm[idx]._row == i && sm._sm[idx]._col == j)
{
cout << sm._sm[idx++]._value << " ";
}
else
{
cout << sm._invalid << " ";
}
}
cout << endl;
}
cout << endl;
return _cout;
}

//给出两种逆置方法的时间复杂度
// 稀疏矩阵的转置
SparseMatrix<T> Transport()//时间复杂度 O(列数*有效元素的个数+有效元素的个数)
{
vector<Trituple<T>> _sm2;
size_t n = _sm.size();
_sm2.resize(n);
size_t count = 0;

for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < n; j++)
{
if (_sm[j]._col == i) //把_sm中列为i的数找出来作为将来的行,以列优先达到复位是的行优先
{
_sm2[count] = _sm[j]; //将数据保存在_sm2中返回是就是按照行优先来计算的
swap(_sm2[count]._row, _sm2[count]._col);//交换坐标
count++;
}
}
}
for (size_t i = 0; i < n; i++)
{
_sm[i] = _sm2[i];//在operator中调用默认的_sm是第一个的,所以还是按照原来的有效值存储顺序打印的,只是将行列交换后,原来每个行列的大小不一样而已,没有改变实质值;
}
swap(_row, _col);
return *this;
}

// 稀疏矩阵的快速转置(时间复杂度:2*有效元素的个数+列数)
SparseMatrix<T> FastTransport()
{
vector<Trituple<T>> _sm2;
size_t n = _sm.size();
_sm2.resize(n);

size_t Count[5] = {0};//统计转置后矩阵每一行的个数(原矩阵每一列的个数)
size_t Start[5] = {0};//统计转置后的矩阵每行在矩阵压缩中存储的位置(统计原矩阵中每一列第一个元素在_sm2中存储的位置)
for (size_t i = 0; i < n; i++)
{
Count[_sm[i]._col]++; //因为在Count初始化的时候全部初始化为0,所以在Count[_sm[i]._col]中找到第几列的时候就是给第几列++;计算出每列的元素个数;
}
for (size_t i = 1; i < _col; i++)
{
Start[i] = Start[i - 1] + Count[i - 1];
}

for (size_t i = 0; i < n; i++)
{
_sm2[Start[_sm[i]._col]] = _sm[i];//利用Start+Count计算出_sm2中每个元素的位置;
swap(_sm2[Start[_sm[i]._col]]._col, _sm2[Start[_sm[i]._col]]._row);
Start[_sm[i]._col]++;
}

for (size_t i = 0; i < n; i++)
{
_sm[i] = _sm2[i];
}
swap(_row, _col);
return *this;
}

// 两个矩阵相加
SparseMatrix<T> operator+(const SparseMatrix<T>& sp)//两个矩阵的行列数相同 { vector<Trituple<T>> _tmp; size_t n1 = _sm.size(); size_t n2 = sp._sm.size(); _tmp.resize(n1 + n2); //有点浪费空间 int i = 0, j = 0, k = 0; while ((i < n1) && (j < n2)) { if (_sm[i]._row == sp._sm[j]._row)//行相等 { if (_sm[i]._col == sp._sm[j]._col)//列相等 { _tmp[k]._row = _sm[i]._row; _tmp[k]._col = _sm[i]._col; _tmp[k]._value = _sm[i]._value + sp._sm[j]._value; i++; j++; k++; } else if (_sm[i]._col < sp._sm[j]._col) //_sm列小于sp列 ,小的先放入_tmp中 { _tmp[k]._row = _sm[i]._row; _tmp[k]._col = _sm[i]._col; _tmp[k]._value = _sm[i]._value; i++; k++; } else { _tmp[k]._row = sp._sm[j]._row; _tmp[k]._col = sp._sm[j]._col; _tmp[k]._value = sp._sm[j]._value; j++; k++; } } else if (_sm[i]._row < sp._sm[i]._row)//_sm的行号小于sp,把这一行的数据都放进去 { _tmp[k]._row = _sm[i]._row; _tmp[k]._col = _sm[j]._col; _tmp[k]._value = _sm[j]._value; i++; k++; } else { _tmp[k]._row = sp._sm[j]._row; _tmp[k]._col = sp._sm[j]._col; _tmp[k]._value = sp._sm[j]._value; j++; k++; } } _sm.resize(k); for (size_t i = 0; i < k; i++) { _sm[i] = _tmp[i]; } return *this; }

private:
vector<Trituple<T>> _sm;
size_t _row;
size_t _col;
T _invalid;
};

void FunTest()
{
int array[6][5] = {
{ 1, 0, 3, 0, 5 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 1, 0, 3, 0, 5 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 } };

int array1[6][5] = {
{ 0, 1, 0, 3, 6 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 1, 0, 3, 6 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 } };

SparseMatrix<int> s((int*)array, 6, 5, 0);
SparseMatrix<int> s1((int*)array1, 6, 5, 0);
s.Acess(1, 1); //访问(1,1)元素
cout << s << endl;
// s.Transport();//进行逆置检测时只能检测一个
// cout << s << endl;
s.FastTransport();
cout << s << endl;
s.operator+(s1); //进行加法运算的时候必须把逆置复原
cout << s << endl;
}

int main()
{
FunTest();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  存储 压缩