特殊矩陣的壓縮儲存及轉置
一、對稱矩陣及其壓縮儲存
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、對稱矩陣壓縮儲存程式碼實現
5、程式執行結果#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; }
二、稀疏矩陣及其壓縮儲存
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;
}
執行結果