稀疏矩陣的壓縮

稀疏矩陣的特點

M*N矩陣,矩陣中有效值的個數遠遠小於無效值的個數,並且這些數據的分佈沒有規律。

例如下面的矩陣

wKioL1cTnG_RdM91AAAM4xL6kRc377.png

稀疏矩陣的壓縮存儲

壓縮矩陣值存儲極少數的有效數據。使用三元組來存儲每一個數據,三元組數據按照矩陣中的位置,以行優先順序依次存放。

則上述矩陣的存儲結構爲

wKiom1cTnG7zs3RTAAAE8k9eW5k161.png

三元組結構

//三元組的定義
template<class T>
struct  Triple
{
public:
	//默認無參構造函數
	Triple()
	{}

	//構造函數
	Triple(const T& d,size_t row=0,size_t col=0)
		:_value(d)
		,_row(row)
		,_col(col)
	{}
public:
	T _value;//數據域
	size_t _row;//行
	size_t _col;//列
};

稀疏矩陣的壓縮

template<class T>
class SparseMatrix
{
public:
	//無參構造函數
	SparseMatrix()
	{}
	SparseMatrix(const T* a, size_t row, size_t col, const T& invalid);
	void Display();//打印
	SparseMatrix<T> Transport();//列轉置
	SparseMatrix<T> FastTransport();//快速轉置
	
private:
	vector<Triple<T>> _a;//三元組類型的順序表
	size_t _rowSize;//行數
	size_t _colSize;//列數
	T _invalid;//非法值
};

//矩陣的壓縮
template<class T>
SparseMatrix<T>::SparseMatrix(const T* a, size_t row, size_t col, const T& invalid)
	:_rowSize(row)
	, _colSize(col)
	, _invalid(invalid)
{
	//行遍歷
	for (size_t i = 0; i < row; i++)
	{
		//列遍歷
		for (size_t j = 0; j < col; j++)
		{
			if (a[i*col + j] != invalid)
			{
				_a.push_back(Triple<T>(a[i*col + j], i, j));
				//不能通過數組創建,但是可以通過數組形式訪問
			}
		}
	}
}


轉置

將原矩陣的行、列互換,也就是將[row][col]和[col][row]位置上的數據對換。

wKiom1cTnn_SDYj9AAAczW2rlyw473.png

列轉置

算法思想:

採用按照被轉置矩陣三元組表A的序列(即轉置後三元組表B的行序)遞增的順序進行轉置,並以此送入轉置後矩陣的算遠足表B中,這樣一來,轉置後矩陣的三元組表B恰好是以“行序爲主序的”.

實現代碼

//列轉置
template<class T>
SparseMatrix<T> SparseMatrix<T>::Transport()
{
	SparseMatrix<T> result;
	//行列size互換
	result._rowSize = _colSize;
	result._colSize = _rowSize;
	result._invalid = _invalid;

	//按照列掃描
	for (size_t j = 0; j < _colSize; j++)
	{
		//在三元組數組中找到相同列的元素
		for (size_t index = 0; index < _a.size(); index++)
		{
			if (_a[index]._col == j)
			{
				result._a.push_back(Triple<T>(_a[index]._value, _a[index]._col, _a[index]._row));
				//按照列優先的順序存到壓縮數組中
			}
		}
	}
	return result;
}

算法分析:

算法的時間主要消耗在雙重循環中,其時間複雜度爲O(_colSize*_a.size)。


一次定位快速轉置

算法思想:

在列轉置中算法的時間浪費主要在雙重循環中,要改善算法的性能,必須去掉雙重循環,使得整個轉置過程通過一次循環來完成。

爲了能使得被轉置的三元組表A中元素一次定位到三元組表B中,需要預先計算一下數據:

1)rowCounts,三元組表A中每一列有效值的個數,即轉置後矩陣三元組表B中每一行有效值的個數。

2)rowStart,三元組表B中每一行有效值的起始位置。


rowStart[col]=rowStart[col-1]+rowCounts[col-1]

上述矩陣的兩個數組爲:

wKiom1cTplWAuHsmAAAPSkwAUhQ338.png

代碼實現:

//快速轉置
template<class T>
SparseMatrix<T> SparseMatrix<T>::FastTransport()
{
	assert(_a.size() < 0);
	SparseMatrix<T> result;
	//行列size互換
	result._rowSize = _colSize;
	result._colSize = _rowSize;
	result._invalid = _invalid;

	//建立rowCounts和rowStart
	int* rowCounts = new int[_colSize];
	int* rowStart = new int[_colSize];
	memset(rowCounts, 0, sizeof(int)*_colSize);//初始化爲0
	memset(rowStart, 0, sizeof(int)*_colSize);//初始化爲0

	result._a.resize(_a.size());//複製順序表_a,容量相同,但是數據不同。
	//初始化
	for (size_t i = 0; i < _colSize; i++)
	{
		rowCounts[_a[i]._col]++;
	}
	rowStart[0] = 0;
	for (size_t i = 1; i < _colSize; i++)
	{
		rowStart[i] = rowCounts[i - 1] + rowStart[i - 1];
	}

	//快速轉置
	size_t index = 0;
	Triple<T> tmp;
	while (index < _a.size())
	{
		int row = _a[index]._col;//行數
		int rowIndex = rowStart[row];//當前行的起始位置

									 //交換行和列
		tmp._value = _a[index]._value;
		tmp._row = _a[index]._col;
		tmp._col = _a[index]._row;

		result._a[rowIndex] = tmp;
		rowStart[row]++;
		index++;
	}
	delete[] rowCounts;
	delete[] rowStart;
	return result;

}

算法分析:

一次定位快速轉置算法時間主要浪費在3個並列的循環中,分別執行了_colSize,_col_Size,_a.size()次,總的時間複雜度爲O(_colSize+_a.size())。遠遠小於O(_colSize*_a.size)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章