稀疏矩陣
什麼是稀疏矩陣:
稀疏矩陣:在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[n] = 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;
}