稀疏矩陣的訪問、普通逆置和快速逆置、還原輸出以及加法
首先稀疏矩陣的概念:
在一個矩陣中(並不要求為方陣)無效元素的個數遠遠大於有效元素的個數,我們稱之為——稀疏矩陣。一般沒有一個明確的界限分開稀疏矩陣和普通矩陣,不過一些人認為:有效元素的個數/無效元素的個數<0.05即可稱之為稀疏矩陣。
跟對稱矩陣壓縮儲存的邏輯相似:我們只需儲存稀疏矩陣的有限元素就行,但有效元素的位置並沒有什麼規律,所以在儲存時還要儲存相應元素的行、列的下標。
可以用結構體實現:
template<class T>
struct Trituple
{
Trituple(size_t row, size_t col, const T & data)
: _row(row)
, _col(col)
, _data(data)
{}
Trituple()
{}
size_t _row;
size_t _col;
T _data;
};
則類中稀疏矩陣的成員應為:
private:
vector<Trituple<T>> _pData;
size_t _row;
size_t _col;
T _invalid;//無效元素的值
建構函式的實現很簡單,我們只需要遍歷一下這個矩陣,將不為無效值的元素的值、行和列儲存就行。這裡採用的方法也是講二位陣列轉為一維陣列進行訪問。
SparseMatrix(int* array, size_t row, size_t col, const T& invalid)
: _row(row)
, _col(col)
, _invalid(invalid)
{
for (size_t i = 0; i < _row; i++)
{
for (size_t j = 0; j < _col; j++)
{
if (array[i*_col + j] != _invalid)
_pData.push_back(Trituple<T>(i, j, array[i*_col + j]));
}
}
}
稀疏矩陣的訪問:
只需判斷需要訪問的行、列在壓縮儲存的一維陣列中有沒有對應的元素,如果有,則輸出該值;如果沒有,則輸出無效值。
T& Access(int row, int col)
{
for (size_t i = 0; i < _pData.size();++i)
{
if (_pData[i]._row == row&&_pData[i]._col == col)
return _pData[i]._data;
}
return _invalid;
}
稀疏矩陣的還原輸出:
同樣,我們只需判斷要訪問的位置是否存在有效元素,有則輸出,無則輸出無效元素。此時我們最好不要直接訪問Acess()函式,這樣的話開銷太大,時間複雜度過高,不是一個優秀的程式碼。所以我們使用vector中的迭代器訪問被壓縮儲存的每一個元素。或者每次進入之後判斷行、列的關係。
template<class T>
friend ostream& operator<<(ostream& os, SparseMatrix<T>& s)
{
size_t index = 0;
for (size_t i = 0; i < s._row; ++i)
{
for (size_t j = 0; j < s._col; ++j)
{
if ((index < s._pData.size()) && (s._pData[index]._row == i) && (s._pData[index]._col == j))
//注意:index<s._pData.size()是為了index訪問不越界。
os << setw(3) << s._pData[index++]._data << " ";
else
os << setw(3) << s._invalid << " ";
}
os << endl;
}
return os;
}
稀疏矩陣的普通逆置:
按列訪問,每一次進入之後判斷壓縮儲存的一維陣列中是否有對應列的元素,有的話,輸出到新空間,並且向後移動。
SparseMatrix<T> Transprot()
{
SparseMatrix<T> temp;
temp._row = _col;
temp._col = _row;
for (size_t i = 0; i < _col;i++)
{
vector<Trituple<T>>::iterator it = _pData.begin();
while (it != _pData.end())
{
if (it->_col == i)//判斷列
{
temp._pData.push_back(Trituple<T>(i, it->_row, it->_data));//輸入時交換行、列
}
it++;
}
}
return temp;
}
稀疏矩陣的快速逆置:
快速逆置的實現是直接將壓縮儲存中的一維陣列的元素的排列方式變成逆置後的樣子,這樣說起來可能有點生硬,看圖:
首先,按列訪問,統計該列是否有有限元素存在,如果有,給對應的列的陣列下邊的值加一
int* _pCount=new int[_col];//最多有_col列
_pCount[it->_col]++;//此時it表示有限元素的迭代器,it->_col表示它的列。
具體程式碼:
int* _pCount = new int[_col];
memset(_pCount, 0, _col*sizeof(_pCount[0]));
for (size_t i = 0; i < _col; i++)/*按列訪問,每列中有多少個有效元素,
本陣列中,有5列,從下標為0的第一列開始計*/
{
vector<Trituple<T>>::iterator it = _pData.begin();/*每次進來it從_pData的開始走,判斷pData
中的元素有沒有列和判斷的列相同的,如果有,則證明:
_pData在檢測的當前列中存在有效元素,給有效元素的下標位置的_pCount
加1,因為陣列有多行,每一列的元素不止一個,每一列可能有多個有效元素
,所以要使用it遍歷_pData。*/
while (it != _pData.end())
{
if (it->_col == i)/*it代表_pData的當前元素的指標,當其列==檢測列時*/
_pCount[it->_col]++;/*給_pCount中it代表的數的下標位置加一,剛好_pCount的下標與有效元素的列相對應*/
it++;
}
}
我們還應該儲存每個有效元素的起始位置,因為原矩陣的列轉置後就變成了新矩陣的行,所以我們只需儲存原矩陣中每個有效元素的起始列就行。且第一列的起始地址始終未0,下一列的起始地址等於上一列的起始地址+上一列的有效元素的個數。
int* _pAddar = new int[_col];
memset(_pAddar, 0, _col*sizeof(_pAddar[0]));/*置0時,因為第一行的起始地址為0,第一列的起始地址就已經置為0*/
for (size_t i = 1; i < _col; i++)//所以從1開始
{
_pAddar[i] = _pAddar[i - 1] + _pCount[i - 1];
}
放置有效元素到新空間–>“逆置”_pData
如果不懂的話仔細看程式碼中的註釋。
for (size_t i = 0; i < _pData.size(); i++)
{
temp._pData[_pAddar[_pData[i]._col]] = Trituple<T>(_pData[i]._col, _pData[i]._row, _pData[i]._data);
/*_pAddar[_pData[i]._col]:
_pAddar儲存的是新陣列中有效元素的起始位置,也是原壓縮陣列有效元素的列的起始位置
所以原陣列第i個元素的列的位置就是新陣列有效元素的行的起始位置
也就是第i個元素在新陣列中的行的起始位置
所以將原陣列的有效元素放置到新陣列其對應的行的起始位置
且儲存的時候將該元素的行列交換*/
_pAddar[_pData[i]._col]++;
/*放置完後,再給新陣列的行的起始位置加一,因為當前行已經有一個元素存入,
它的起始位置應該向後移動一位
按行優先順序訪問,該行下一個元素在該元素的下個位置*/
}
兩個同行同列的矩陣的相加其實就如:兩個有序單鏈表合併後依然有序的演算法一樣。
將同行同列的元素相加,並push入物件中,但注意:如果加起來為無效值的話則略過不計;如果行、列值不等,則push小的那個。結束迴圈的條件為:任何一個已經遍歷完畢(雖然同行同列,但是有效元素的數目不一定相同)。然後將未遍歷完的矩陣的有限元素直接push入就可。
SparseMatrix<T> operator+(const SparseMatrix<T>& sp)
{
SparseMatrix<T> temp;
temp._row = _row;
temp._col = _col;
size_t i = 0, j = 0;
size_t Size1 = _pData.size();
size_t Size2 = sp._pData.size();
while (i < Size1 && j < Size2)
{
if ((_pData[i]._row == sp._pData[j]._row) && (_pData[i]._col == sp._pData[j]._col))
{
if (_pData[i]._data + sp._pData[i]._data!=_invalid)
temp._pData.push_back((Trituple<T>(_pData[i]._row, _pData[i]._col, _pData[i]._data + sp._pData[j]._data)));
i++; j++;
}
else if ((_pData[i]._row > sp._pData[j]._row) || (_pData[i]._col > sp._pData[j]._col))
{
temp._pData.push_back(sp._pData[j]);
j++;
}
else if ((_pData[i]._row < sp._pData[j]._row) || (_pData[i]._col < sp._pData[j]._col))
{
temp._pData.push_back(_pData[i]);
i++;
}
}
if (i >= Size1)
{
for (; j < Size2; j++)
temp._pData.push_back(sp._pData[j]);
}
if (j >= Size2)
{
for (; i < Size1; i++)
temp._pData.push_back(_pData[i]);
}
return temp;
}
關於稀疏矩陣的所有程式碼以及測試用例如下:
#include <vector>
#include <iomanip>
#include <iostream>
using namespace std;
template<class T>
class SparseMatrix
{
template<class T>
struct Trituple
{
Trituple(size_t row, size_t col, const T& data)
: _row(row)
, _col(col)
, _data(data)
{}
Trituple()
{}
size_t _row;
size_t _col;
T _data;
};
public:
// 稀疏矩陣的壓縮儲存
SparseMatrix(int* array, size_t row, size_t col, const T& invalid)
: _row(row)
, _col(col)
, _invalid(invalid)
{
for (size_t i = 0; i < _row; i++)
{
for (size_t j = 0; j < _col; j++)
{
if (array[i*_col + j] != _invalid)
_pData.push_back(Trituple<T>(i, j, array[i*_col + j]));
}
}
}
SparseMatrix()
{}
// 訪問稀疏矩陣中row行col中的元素
T& Access(int row, int col)
{
for (size_t i = 0; i < _pData.size();++i)
{
if (_pData[i]._row == row&&_pData[i]._col == col)
return _pData[i]._data;
}
return _invalid;
}
// 稀疏矩陣的逆置
SparseMatrix<T> Transprot()
{
SparseMatrix<T> temp;
temp._row = _col;
temp._col = _row;
for (size_t i = 0; i < _col;i++)
{
vector<Trituple<T>>::iterator it = _pData.begin();
while (it != _pData.end())
{
if (it->_col == i)
{
temp._pData.push_back(Trituple<T>(i, it->_row, it->_data));
}
it++;
}
}
return temp;
}
// 稀疏矩陣的快速逆置
SparseMatrix<T> FastTransprot()
{
SparseMatrix<T> temp;
temp._row = _col;
temp._col = _row;
for (size_t i = 0; i < _pData.size(); i++)
temp._pData.push_back(Trituple<T>());
//統計每列中有效元素的個數
int* _pCount = new int[_col];
memset(_pCount, 0, _col*sizeof(_pCount[0]));
for (size_t i = 0; i < _col; i++)/*按列訪問,每列中有多少個有效元素*/
{
vector<Trituple<T>>::iterator it = _pData.begin();/*每次進來it從_pData的開始走,判斷pData
中的元素有沒有列和判斷的列相同的,如果有,則證明:
_pData在檢測的當前列中存在有效元素,給有效元素的下標位置的_pCount
加1,因為陣列有多行,每一列的元素不止一個,每一列可能有多個有效元素
,所以要使用it遍歷_pData。*/
while (it != _pData.end())
{
if (it->_col == i)/*it代表_pData的當前元素的指標,當其列==檢測列時*/
_pCount[it->_col]++;/*給_pCount中it代表的數的下標位置加一,剛好_pCount的下標與有效元素的列相對應*/
it++;
}
}
//用陣列儲存每列元素在新矩陣中的起始地址
int* _pAddar = new int[_col];
memset(_pAddar, 0, _col*sizeof(_pAddar[0]));/*置0時,因為第一行的起始地址為0,第一列的起始地址就已經置為0*/
for (size_t i = 1; i < _col; i++)//所以從1開始
{
_pAddar[i] = _pAddar[i - 1] + _pCount[i - 1];
}
//放置有效元素到新空間-->“逆置”_pData
for (size_t i = 0; i < _pData.size(); i++)
{
temp._pData[_pAddar[_pData[i]._col]] = Trituple<T>(_pData[i]._col, _pData[i]._row, _pData[i]._data);
/*_pAddar[_pData[i]._col]:
_pAddar儲存的是新陣列中有效元素的起始位置,也是原壓縮陣列有效元素的列的起始位置
所以原陣列第i個元素的列的位置就是新陣列有效元素的行的起始位置
也就是第i個元素在新陣列中的行的起始位置
所以將原陣列的有效元素放置到新陣列其對應的行的起始位置
且儲存的時候將該元素的行列交換*/
_pAddar[_pData[i]._col]++;
/*放置完後,再給新陣列的行的起始位置加一,因為當前行已經有一個元素存入,
它的起始位置應該向後移動一位
按行優先順序訪問,該行下一個元素在該元素的下個位置*/
}
return temp;
}
// 實現稀疏矩陣的加法操作
SparseMatrix<T> operator+(const SparseMatrix<T>& sp)
{
SparseMatrix<T> temp;
temp._row = _row;
temp._col = _col;
size_t i = 0, j = 0;
size_t Size1 = _pData.size();
size_t Size2 = sp._pData.size();
while (i < Size1 && j < Size2)
{
if ((_pData[i]._row == sp._pData[j]._row) && (_pData[i]._col == sp._pData[j]._col))
{
if (_pData[i]._data + sp._pData[i]._data!=_invalid)
temp._pData.push_back((Trituple<T>(_pData[i]._row, _pData[i]._col, _pData[i]._data + sp._pData[j]._data)));
i++; j++;
}
else if ((_pData[i]._row > sp._pData[j]._row) || (_pData[i]._col > sp._pData[j]._col))
{
temp._pData.push_back(sp._pData[j]);
j++;
}
else if ((_pData[i]._row < sp._pData[j]._row) || (_pData[i]._col < sp._pData[j]._col))
{
temp._pData.push_back(_pData[i]);
i++;
}
}
if (i >= Size1)
{
for (; j < Size2; j++)
temp._pData.push_back(sp._pData[j]);
}
if (j >= Size2)
{
for (; i < Size1; i++)
temp._pData.push_back(_pData[i]);
}
return temp;
}
// 還原稀疏矩陣
template<class T>
friend ostream& operator<<(ostream& os, SparseMatrix<T>& s)
{
size_t index = 0;
for (size_t i = 0; i < s._row; ++i)
{
for (size_t j = 0; j < s._col; ++j)
{
if ((index < s._pData.size()) && (s._pData[index]._row == i) && (s._pData[index]._col == j))
os << setw(3) << s._pData[index++]._data << " ";
else
os << setw(3) << s._invalid << " ";
}
os << endl;
}
return os;
}
private:
vector<Trituple<T>> _pData;
size_t _row;
size_t _col;
T _invalid;
};
int main()
{
int array[][5] = {
{ 0, 6, 0, 0, 0 },
{ 1, 0, 0, 0, 0 },
{ 0, 2, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 4, 0, 0, 0 },
{ 0, 0, 0, 7, 0 } };
int array1[][5] = {
{ 0, 0, 0, 0, 0 },
{ 0, 2, 0, 0, 0 },
{ 0, 0, 0, 4, 0 },
{ 0, 0, 7, 0, 5 },
{ 0, 6, 0, 0, 0 },
{ 0, 0, 5, 0, 0 } };
SparseMatrix<int> sp((int*)array, 6, 5, 0);
cout << sp << endl;
cout << sp.Transprot() << endl;
cout << sp.FastTransprot() << endl;
SparseMatrix<int> sp1((int*)array1, 6, 5, 0);
SparseMatrix<int> sp2 = sp + sp1;
cout << sp2 << endl;
system("pause");
return 0;
}
如有問題,敬請指出。