一、对称矩阵及其压缩存储
1、对称矩阵
在矩阵中最特殊的一类应该属于对称矩阵,对称矩阵的行和列是对应相等的。对称矩阵就是关于主对角线对称,两边的元素的值对应相等,在数学中我们把用主对角线隔开,一方全是0,一方是非零值的元素,分为上三角和下三角.
2、对称矩阵的压缩存储
首先来说一下我们为什么要进行压缩存储,对于对称矩阵来说,关于主对角线两边的元素来说会造成巨大的数据冗余,在存储的过程中会造成存储空间的巨大浪费。因此对于一个一个n×n的矩阵,使用压缩存储的方法,只需要存储下三角处的元素,即只需要n×(n-1)/2多数据的存储空间。
那么对于任意一个位置的元素Aij,只要i>=j都有Aij=arr[i*(i-1)/2+j].
3、对称矩阵存储举例
0 1 2 3 4
1 0 1 2 3
2 1 0 1 2
3 2 1 0 1
4 3 2 1 0
4、对称矩阵压缩存储代码实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
template<class T>
class SymmetricMatrix
{
public:
SymmetricMatrix(T* arr, size_t n)//传入对称矩阵
: _data(new T[n * (n + 1) / 2])//
, _n(n)
{
size_t index = 0;
for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < n; j++)
{
if (i >= j)//去下三角所在出的元素
{
_data[index] = arr[i*n + j];
index++;
}
else//上三角处的元素不作处理
{
break;
}
}
}
}
T& Access(size_t i, size_t j)//获取上三角的元素
{
if (i >= j)
{
return _data[i*(i + 1) / 2 + j];
}
else//关于主对角线对称
{
swap(i, j);
return _data[i*(i + 1) / 2 + j];
}
}
void PrintSymmetricMatrix()
{
for (size_t i = 0; i < _n; i++)
{
for (size_t j = 0; j < _n; j++)
{
cout << Access(i, j) << " ";
}
cout << endl;
}
cout << endl;
}
~SymmetricMatrix()
{
if (NULL != _data)
{
delete[] _data;
_data = NULL;
}
}
protected:
T* _data;
size_t _n;
};
void TestSymmetricMatrix()
{
int a[5][5] =
{
{0, 1, 2, 3, 4},
{1, 0, 1, 2, 3},
{2, 1, 0, 1, 2},
{3, 2, 1, 0, 1},
{4, 3, 2, 1, 0},
};
SymmetricMatrix<int> s1((int*)a, 5);
s1.PrintSymmetricMatrix();
}
int main()
{
TestSymmetricMatrix();
system("pause");
return 0;
}
5、程序运行结果
二、稀疏矩阵及其压缩存储
1、稀疏矩阵
简单来说稀疏矩阵就是无效元素值远远大于有效元素值的个数,对于一个整形的稀疏矩阵而言,所谓的无效值远远大于有效值即0值远远大于非零值.
2、稀疏矩阵的压缩存储
和对称矩阵一样,如果我们将稀疏矩阵中所有的元素都存储起来,那么我们所需要的有效数据的个数有可能只占其中的一小部分,这种情况会造成极大地空间浪费和数据冗余。因此在稀疏矩阵的压缩存储中,只取其有效元素所在的行列和有效值,由此构成存储的压缩矩阵。
要实现这个存储方式,需要自定义一个三元组这样的结构体,用来存放{row,col,valid},按照行优先的原则一次存放有效元素的值。
3、稀疏矩阵的压缩存储实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
struct Triple//定义一个三元组结构体
{
Triple(size_t row, size_t col, T& value)
: _row(row)
, _col(col)
, _value(value)
{}
Triple()
{}
size_t _row;
size_t _col;
T _value;
};
template<class T>
class SparseMatrix//稀疏矩阵
{
public:
SparseMatrix(T* arr, size_t m, size_t n, const T& invalid)
: _a(NULL)
, _n(n)
, _m(m)
, _invalid(invalid)
{
size_t index = 0;
for (size_t i = 0; i < m; i++)
{
for (size_t j = 0; j < n; j++)
{
if (arr[i*n+j] != invalid)//判断所取位置的值是否合法
{
_a.push_back(Triple<int>(i, j, arr[i*n + j]));
}
}
}
}
void PrintSparseMatrix()
{
size_t index = 0;
for (size_t i = 0; i < _m; i++)
{
for (size_t j = 0; j < _n; j++)
{
if (index < _a.size()&&//确保其不会在没遇到有效元素时发生溢出
_a[index]._row == i&&
_a[index]._col == j)
{
cout << _a[index]._value << " ";
index++;
}
else
{
cout << _invalid << " ";
}
}
cout << endl;
}
cout << endl;
}
protected:
vector<Triple<T>> _a;
size_t _m;//行
size_t _n;//列
T _invalid;//默认的无效值
};
void TestSparseMatrix()
{
int array [6][5] =
{
{1, 0, 3, 0, 5},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{2, 0, 4, 0, 6},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
SparseMatrix<int> s2((int*)array, 6, 5, 0);
s2.PrintSparseMatrix();
}
int main()
{
TestSparseMatrix();
system("pause");
return 0;
}
4、运行结果
三、稀疏矩阵的普通逆置和快速逆置
1、稀疏矩阵的普通逆置
简单来说矩阵逆置就是将矩阵的行和列进行简单的交换,原来在行上的元素逆置后换到列上,原来在列上的元素逆置后换到行上,普通的逆置就是采用这种思想来完成的,下面直接来看这种普通逆置的代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
struct Triple//定义一个三元组结构体
{
Triple(size_t row, size_t col, T& value)
: _row(row)
, _col(col)
, _value(value)
{}
Triple()
{}
size_t _row;
size_t _col;
T _value;
};
template<class T>
class SparseMatrix//稀疏矩阵
{
public:
SparseMatrix()
: _a(NULL)
, _n(0)
, _m(0)
, _invalid(T())
{}
SparseMatrix(T* arr, size_t m, size_t n, const T& invalid)
: _a(NULL)
, _m(m)
, _n(n)
, _invalid(invalid)
{
size_t index = 0;
for (size_t i = 0; i < m; i++)
{
for (size_t j = 0; j < n; j++)
{
if (arr[i*n + j] != invalid)//判断所取位置的值是否合法
{
_a.push_back(Triple<int>(i, j, arr[i*n + j]));
}
}
}
}
void PrintSparseMatrix()
{
size_t index = 0;
for (size_t i = 0; i < _m; i++)
{
for (size_t j = 0; j < _n; j++)
{
if (index < _a.size() &&//确保其不会在没遇到有效元素时发生溢出
_a[index]._row == i&&
_a[index]._col == j)
{
cout << _a[index]._value << " ";
index++;
}
else
{
cout << _invalid << " ";
}
}
cout << endl;
}
cout << endl;
}
SparseMatrix<T> Transport()//普通的转置,进行简单的行列互换
{
SparseMatrix<T> tmp;
tmp._m = _n;
tmp._n = _m;
tmp._a.reserve(_a.size());//之开辟所需大小的容量
for (size_t i = 0; i < _n; ++i)
{
size_t index = 0;
while (index < _a.size())
{
if (_a[index]._col == i)//相当于按照列打印
{
Triple<T> tmp1(_a[index]._col, _a[index]._row, _a[index]._value);
tmp._a.push_back(tmp1);
}
++index;
}
}
return tmp;
}
protected:
vector<Triple<T>> _a;
size_t _m;//行
size_t _n;//列
T _invalid;//默认的无效值
};
void TestSparseMatrix()
{
int array[6][5] =
{
{ 1, 0, 3, 0, 5 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 2, 0, 4, 0, 6 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
SparseMatrix<int> s2((int*)array, 6, 5, 0);
s2.PrintSparseMatrix();
SparseMatrix<int> s1;
s1 = s2.Transport();
s1.PrintSparseMatrix();
}
int main()
{
TestSparseMatrix();
system("pause");
return 0;
}
运行结果:
2、稀疏矩阵的快速逆置
快速逆置的思想:
第一步:统计出转置之后的矩阵每一行有效值的个数count,并将有效数据直接定位到新三元组的对应位置处,此时的时间复杂度为O(2*有效值的个数+N)
第二步:用count统计新矩阵每一行的有效值的个数等价于统计原来矩阵的每一列的有效值的个数,通过遍历原来的三元组,将原来三元组的列下标作为count的下标,只要原来三元组的列存在有效值该count的对应下标就+1.
第三步:start找到新矩阵的每一行的第一个元素在新三元组的存储下标.
第四步:此时再次遍历原来的三元组,将该数据直接放入新三元组对应的下标处,此时将start位置的存储数据+1,防止插入到相同的位置抹掉以前存放的数据.
3、快速逆置代码实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
struct Triple//定义一个三元组结构体
{
Triple(size_t row, size_t col, T& value)
: _row(row)
, _col(col)
, _value(value)
{}
Triple()
{}
size_t _row;
size_t _col;
T _value;
};
template<class T>
class SparseMatrix//稀疏矩阵
{
public:
SparseMatrix()
: _a(NULL)
, _n(0)
, _m(0)
, _invalid(T())
{}
SparseMatrix(T* arr, size_t m, size_t n, const T& invalid)
: _a(NULL)
, _m(m)
, _n(n)
, _invalid(invalid)
{
size_t index = 0;
for (size_t i = 0; i < m; i++)
{
for (size_t j = 0; j < n; j++)
{
if (arr[i*n + j] != invalid)//判断所取位置的值是否合法
{
_a.push_back(Triple<int>(i, j, arr[i*n + j]));
}
}
}
}
void PrintSparseMatrix()
{
size_t index = 0;
for (size_t i = 0; i < _m; i++)
{
for (size_t j = 0; j < _n; j++)
{
if (index < _a.size() &&//确保其不会在没遇到有效元素时发生溢出
_a[index]._row == i&&
_a[index]._col == j)
{
cout << _a[index]._value << " ";
index++;
}
else
{
cout << _invalid << " ";
}
}
cout << endl;
}
cout << endl;
}
SparseMatrix<T> Transport()//普通的转置,进行简单的行列互换
{
SparseMatrix<T> tmp;
tmp._m = _n;
tmp._n = _m;
tmp._a.reserve(_a.size());//之开辟所需大小的容量
for (size_t i = 0; i < _n; ++i)
{
size_t index = 0;
while (index < _a.size())
{
if (_a[index]._col == i)//相当于按照列打印
{
Triple<T> tmp1(_a[index]._col, _a[index]._row, _a[index]._value);
tmp._a.push_back(tmp1);
}
++index;
}
}
return tmp;
}
SparseMatrix<T> FastTransport()//快速排序
{
SparseMatrix<T> ftmp;
ftmp._m = _n;
ftmp._n = _m;
ftmp._a.resize(_a.size());//存储有效元素的个数不发生改变
int *count = new int[_n];
memset(count, 0, sizeof(int)*_n);//给开辟的空间赋初值
for (size_t i = 0; i < _a.size(); ++i)
{
int col = _a[i]._col;
++count[col];
}
int* start = new int[_n];//记录新矩阵每行第一个元素在三元组中的存储位置
memset(start, 0, sizeof(int)*_n);
size_t i = 0;
start[i] = 0;//第一列下标为0
for (i = 1; i < _n; ++i)
{
//每列的初始坐标为上一列初始坐标+上一列有效元素的个数
start[i] = start[i - 1] + count[i - 1];
}
//遍历三元组找到数据就放到新三元组的下标处
for (size_t i = 0; i < _a.size(); ++i)
{
int col = _a[i]._col;
size_t tmp = start[col];
ftmp._a[tmp]._row = _a[i]._col;
ftmp._a[tmp]._col = _a[i]._row;
ftmp._a[tmp]._value = _a[i]._value;
++start[col];//防止同一行中有多个数据
}
delete[] start;
delete[] count;
return ftmp;
}
protected:
vector<Triple<T>> _a;
size_t _m;//行
size_t _n;//列
T _invalid;//默认的无效值
};
void TestSparseMatrix()
{
int array[6][5] =
{
{ 1, 0, 3, 0, 5 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 2, 0, 4, 0, 6 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
SparseMatrix<int> s2((int*)array, 6, 5, 0);
s2.PrintSparseMatrix();
SparseMatrix<int> s1;
s1 = s2.Transport();
s1.PrintSparseMatrix();
SparseMatrix<int> s3;
s3 = s2.FastTransport();
s3.PrintSparseMatrix();
}
int main()
{
TestSparseMatrix();
system("pause");
return 0;
}
运行结果