C++实现矩阵压缩存储与(快速)转置

  注意:以下所有代码均在VS2010环境下运行测试

     

      了解了C语言以后,我们都知道,要存储一个矩阵,用一个二维数组即可实现,今天,由我来带领大家玩点新鲜的,对矩阵进行压缩存储并对其进行转置。


一、对称矩阵及对称矩阵的压缩存储


1、什么是对称矩阵?

       设一个N*N的方阵A,A中任意元素Aij,当且仅当Aij == Aji(0 <= i <= N-1

&& 0 <= j <= N-1),则矩阵A是对称矩阵。以矩阵的对角线为分隔,分为上三
角和下三角。


2、对称矩阵的压缩存储

       压缩存储称矩阵存储时只需要存储上三角/下三角的数据,所以最多存
n(n+1)/2个数据(相当于1+2+...+n,即等差数列求和)。
      

     对称矩阵和压缩存储的对应关系:下三角存储i>=j, SymmetricMatrix[i][j] ==

Array[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、代码实现对称矩阵的压缩存储

#include 
using namespace std;

template 
class SymmetricMatrix
{
public:
	SymmetricMatrix(T* arr,size_t n)
		:_data(new T[n*(n+1)/2])//已知空间大小为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)//下三角元素
		{
			//由对称矩阵可得i>=j, A[i][j] ==_data[i*(i+1)/2+j]
			return _data[i*(i+1)/2+j];
		}
		else//上三角元素
		{
			swap(i,j);//行列交换
			return _data[i*(i+1)/2+j];
		}
	}
	void PrintSymmetricMatrix()
	{
		cout<<"SymmetricMatrix:"<<_n<<"行"<<_n<<"列"< sy((int*)a,5);
	sy.PrintSymmetricMatrix();
}
int main()
{
	TestSymmetricMatrix();
	system("pause");
	return 0;
}

运行结果:

C++实现矩阵压缩存储与(快速)转置_第1张图片


二、稀疏矩阵及稀疏矩阵的压缩存储


1、什么样的矩阵称为稀疏矩阵?

      一个M行N列(即M*N)的矩阵,矩阵中有效值的个数远小于无效值的个数,且这些数据的分布没有规律

例如:一个6行5列的稀疏矩阵(M = 6,N = 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


2、稀疏矩阵的压缩存储

      压缩存储只存储极少数的有效数据。使用{row,col,value}三元组存储每一个有效
数据,三元组按原矩阵中的位置,以行优先级先后顺序依次存放。


3、代码实现稀疏矩阵的压缩存储

#include 
#include 
using namespace std;

template 
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 SparseMatrix//稀疏矩阵
{
public:
	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(i,j,arr[i*n+j]));
				}
			}
		}
	}
	void PrintSparseMatrix()
	{
		size_t index = 0;
		cout<<"SparseMatrix:"<<_m<<"行"<<_n<<"列"<> _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},
	{1, 0, 3, 0, 5},
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0}};
	SparseMatrix sp((int*)array,6,5,0);
	sp.PrintSparseMatrix();
}
int main()
{
	TestSparseMatrix();
	system("pause");
	return 0;
}
运行结果:

C++实现矩阵压缩存储与(快速)转置_第2张图片

4、稀疏矩阵的转置

将原矩阵的行、列对换,也就是将[i][j]和[j][i]位置上的数据对换。

如:

C++实现矩阵压缩存储与(快速)转置_第3张图片

算法分析

C++实现矩阵压缩存储与(快速)转置_第4张图片

代码实现:

#include 
#include 
using namespace std;

template 
struct Triple//三元组
{
	Triple(size_t row = 0,size_t col = 0,T value = T())
		:_row(row)
		,_col(col)
		,_value(value)
	{}

	size_t _row;
	size_t _col;
	T _value;
};

template 
class SparseMatrix//稀疏矩阵
{
public:
	SparseMatrix()
		:_a(NULL)
		,_m(0)
		,_n(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(i,j,arr[i*n+j]));
				}
			}
		}
	}
	void PrintSparseMatrix()
	{
		size_t index = 0;
		cout<<"SparseMatrix:"<<_m<<"行"<<_n<<"列"< Transport()   //矩阵的转置--普通算法
	{
		SparseMatrix tsm;
		tsm._m = _n;
		tsm._n = _m;
		tsm._a.reserve(_a.size());//开辟有效元素个空间
		for (size_t i = 0;i < _n; ++i)
		{
			//i控制新矩阵的行也就是旧矩阵的列
			size_t index=0;
			while (index < _a.size())
			{
				//index控制原来三元组的元素个数
				if (_a[index]._col == i)
				{
					//遍历原来的三元组如果存在满足对应下标的元素则进入新的三元组.
					Triple tmp(_a[index]._col,_a[index]._row,_a[index]._value);
					tsm._a.push_back(tmp);
				}
				++index;
			}
		}
		return tsm;
	}

protected:
	vector> _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 sp((int*)array,6,5,0);
	sp.PrintSparseMatrix();
	
	SparseMatrix sp1;
	sp1 = sp.Transport();
	sp1.PrintSparseMatrix();
}
int main()
{
	TestSparseMatrix();
	system("pause");
	return 0;
}
运行结果:

C++实现矩阵压缩存储与(快速)转置_第5张图片


5、稀疏矩阵的快速转置

      上面我们实现了对稀疏矩阵的转置,但是仔细观看代码,你会发现它做了很多无用功,单纯把三元组遍历了col次,那么有没有一种更快速更高效的算法呢?是的,它就是接来来的快速转置。


算法思想

      (1)统计出转置之后的矩阵每一行有效值的个数count,并将有效数据直接定位到新三元组的对应位置处,此时的时间复杂度为O(2*有效值的个数+N)

      (2)用count统计新矩阵每一行的有效值的个数等价于统计原来矩阵的每一列的有效值的个数,通过遍历原来的三元组,将原来三元组的列下标作为count的下标,只要原来三元组的列存在有效值该count的对应下标就+1.

      (3)start找到新矩阵的每一行的第一个元素在新三元组的存储下标.

      (4)此时再次遍历原来的三元组,将该数据直接放入新三元组对应的下标处,此时将start位置的存储数据+1,防止插入到相同的位置抹掉以前存放的数据.

算法分析
C++实现矩阵压缩存储与(快速)转置_第6张图片

代码实现
#include 
#include 
using namespace std;

template 
struct Triple//三元组
{
	Triple(size_t row = 0,size_t col = 0,T value = T())
		:_row(row)
		,_col(col)
		,_value(value)
	{}

	size_t _row;
	size_t _col;
	T _value;
};

template 
class SparseMatrix//稀疏矩阵
{
public:
	SparseMatrix()
		:_a(NULL)
		,_m(0)
		,_n(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(i,j,arr[i*n+j]));
				}
			}
		}
	}
	void PrintSparseMatrix()
	{
		size_t index = 0;
		cout<<"SparseMatrix:"<<_m<<"行"<<_n<<"列"< Transport()   //矩阵的转置--普通算法
	{
		SparseMatrix tsm;
		tsm._m = _n;
		tsm._n = _m;
		tsm._a.reserve(_a.size());//开辟有效元素个空间
		for (size_t i = 0;i < _n; ++i)
		{
			//i控制新矩阵的行也就是旧矩阵的列
			size_t index=0;
			while (index < _a.size())
			{
				//index控制原来三元组的元素个数
				if (_a[index]._col == i)
				{
					//遍历原来的三元组如果存在满足对应下标的元素则进入新的三元组.
					Triple tmp(_a[index]._col,_a[index]._row,_a[index]._value);
					tsm._a.push_back(tmp);
				}
				++index;
			}
		}
		return tsm;
	}

	SparseMatrix FastTransport()     //矩阵的转置--快速算法
	{
		SparseMatrix ftsm;
		ftsm._m = _n;    //新矩阵的行就是旧矩阵的列
		ftsm._n = _m;
		ftsm._a.resize(_a.size());

		//count统计新矩阵每一行有效值的个数
		int *count = new int[_n];
		memset(count,0,sizeof(int)*_n);    //将count空间初值赋值为0
		
		for (size_t i = 0; i < _a.size(); ++i)//记录每列有效元素的个数
		{
			int col = _a[i]._col;
			++count[col];
		}
		
		int *start = new int[_n];	//start记录新矩阵的每行第一个元素在三元组的存储下标
		memset(start,0,sizeof(int)*_n);//将start空间初值赋值为0
		
		size_t i = 0;
		start[i] = 0;//第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];
			
			ftsm._a[tmp]._row = _a[i]._col;
			ftsm._a[tmp]._col = _a[i]._row;
			ftsm._a[tmp]._value = _a[i]._value;
			
			++start[col];			//防止同一行的有效数据放入相同位置
		}
		
		delete[]start;
		delete[]count;
		
		return ftsm;
	}


protected:
	vector> _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 sp((int*)array,6,5,0);
	sp.PrintSparseMatrix();
	
	SparseMatrix sp1;
	sp1 = sp.Transport();
	sp1.PrintSparseMatrix();

	SparseMatrix sp2;
	sp2 = sp.FastTransport();
	sp2.PrintSparseMatrix();
}
int main()
{
	TestSparseMatrix();
	system("pause");
	return 0;
}
运行结果:
C++实现矩阵压缩存储与(快速)转置_第7张图片




你可能感兴趣的:(数据结构,数据结构)