1. 稀疏矩陣的概念
當我們利用一個矩陣來描述一個實際問題時,往往矩陣中存在許多“0”值,如下圖所示。試想,當矩陣維數非常大時,利用一個二維數組來存儲此矩陣會浪費很多額外的空間。由此,在參考文獻[1]中,藉助於鏈表來存儲稀疏矩陣。
通過行節點鏈表和頭節點鏈表存儲一個稀疏矩陣。其中:
- 在稀疏矩陣的每一行中,若該行存在不爲0的元素,則爲改行創建一個行節點鏈表。鏈表中每個節點有3個域:矩陣元素值(值域value)、元素所在的列(col)、指向下一個節點的指針域。CNode類爲每個節點的描述,對於矩陣中的每一行可被定義爲
Chain<CNode<T>> 類型。 對於稀疏矩陣的每一非零行的頭節點可以藉助於頭節點鏈表連接起來,定義爲
Chain<HeadNode<T>> 類型。鏈表中的每個節點有三個域:元素所在的行數(row)、Chain<CNode<T>> 類的對象(對應於每行的行鏈表)、指向下一行的指針。注:Chain類爲前面所述的鏈表類(見鏈接—鏈表類)。
2、建立行節點類
/*---------------------------------行節點類-------------------------------------*/
/*
在稀疏矩陣的每一行中,若該行存在不爲0的元素,則爲改行創建一個鏈表。鏈表中每個節點有
3個域:矩陣元素值(值域value)、元素所在的列(col)、指向下一個節點的指針域。CNode類
爲每個節點的描述,對於矩陣中的每一行可被定義爲Chain<CNode<T>>類型。Chain類爲前面所述
的鏈表類(存在於Chain.h文件中)。
*/
template <class T> class LinkedMatrix; //聲明稀疏矩陣類,爲了將其聲明爲CNode類的友類,LinkedMatrix的聲明與定義見後文
template <class T>
class CNode
{
friend LinkedMatrix<T>; //聲明LinkedMatrix爲CNode的友類
//在此說明,在聲明模板類的友元函數時,在聲明之前要加上模板類的聲明,否則會出現“無法加載外部符號”錯誤
template <class T>
friend ostream& operator<< //聲明重載操作符“<<”函數爲CNode的友元函數
(ostream&, const LinkedMatrix<T>&);
friend LinkedMatrix<T>;
template <class T>
friend istream& operator>> //聲明重載操作符“>>”函數爲CNode的友元函數
(istream&, LinkedMatrix<T>&);
public:
int operator!=(const CNode<T> &y) { return (value != y.value); }
void Output(ostream &out) const //送給輸出流
{
out << "column= " << col << "value= " << value;
}
private:
T value; //節點中的“值”域
int col; //節點所在列數
};
//輸出每個行節點
template <class T>
ostream &operator<<(ostream& out, const CNode<T> &x)
{
x.Output(out); //輸出
out << endl; //換行
return out;
}
3、聲明頭節點類:
/*-------------------------------頭節點鏈表類-------------------------------------*/
/*
對於稀疏矩陣的每一非零行的頭節點可以藉助於頭節點鏈表連接起來,定義爲Chain<HeadNode<T>>
類型。鏈表中的每個節點有三個域:元素所在的行數(row)、Chain<CNode<T>>類的對象(對應於
每行的行鏈表)、指向下一行的指針。
*/
template <class T> class LinkedMatrix; //聲明稀疏矩陣類,爲了將其聲明爲CNode類的友類,LinkedMatrix的聲明與定義見後文
template <class T>
class HeadNode
{
friend LinkedMatrix<T>; //聲明LinkedMatrix爲CNode的友類
//在此說明,在聲明模板類的友元函數時,在聲明之前要加上模板類的聲明,否則會出現“無法加載外部符號”錯誤
template <class T>
friend ostream& operator<< //聲明重載操作符“<<”函數爲CNode的友元函數
(ostream&, const LinkedMatrix<T>&);
friend LinkedMatrix<T>;
template <class T>
friend istream& operator>> //聲明重載操作符“>>”函數爲CNode的友元函數
(istream&, LinkedMatrix<T>&);
public:
int operator!=(const HeadNode<T> &y)
{
return (row != y.row);
}
void Output(ostream &out) const
{
out << "row" << row;
}
private:
int row; //元素所在的行數
Chain<CNode<T>> a; //行鏈表
};
//輸出每個頭節點的行數
template <class T>
ostream& operator<<(ostream &out, const HeadNode<T> &x)
{
x.Output(out); //輸出
out << endl; //換行
return out;
}
4、稀疏矩陣鏈表類
4.1 稀疏矩陣鏈表類的聲明
/*------------------------------稀疏矩陣鏈表類------------------------------------*/
template <class T>
class LinkedMatrix
{
//在此說明,在聲明模板類的友元函數時,在聲明之前要加上模板類的聲明,否則會出現“無法加載外部符號”錯誤
template <class T> //友元函數,重載“<<”操作符,用於輸入稀疏矩陣非零元素
friend ostream& operator<<(ostream&, const LinkedMatrix<T>&);
template <class T> //友元函數,重載“>>”操作符,用於輸出稀疏矩陣的非零元素
friend istream& operator>>(istream&, LinkedMatrix<T>&);
public:
LinkedMatrix() {};
~LinkedMatrix() {};
void Transpose(LinkedMatrix<T> &b) const; //求稀疏矩陣的轉置
private:
int rows, cols; //矩陣維數
Chain<HeadNode<T>> a; //頭節點鏈表
};
4.2 輸入一個稀疏矩陣
//從輸入流中輸入一個稀疏矩陣
template <class T>
istream& operator>>(istream &in, LinkedMatrix<T> &x)
{
x.a.Erase(); //清空所有節點
//獲取矩陣特徵
int terms; //非零元素數
cout << "請輸入矩陣的行數、列數及非零元素數:" << endl;
in >> x.rows >> x.cols >> terms;
//虛設第0行
HeadNode<T> H; //當前行的頭節點
H.row = 0; //當前行號
//輸入非零元素
for (int i = 1; i <= terms; i++)
{
//輸入下一個元素
cout << "請輸入行數,列數和元素值:" << i << endl;
int row, col;
T value;
in >> row >> col >> value;
//檢查新元素是否屬於當前行
if (row > H.row) //不屬於當前行,開始一個新行
{
if (H.row) //如果當前行不是第0行,將當前行的頭節點添加到頭節點鏈表x.a中
x.a.Append(H);
H.row = row; //置當前節點行數爲新行數
H.a.Zero(); //指向頭節點first=0
}
CNode<T> *c = new CNode<T>; //操作行節點
c->col = col;
c->value = value;
H.a.Append(*c); //添加到頭節點鏈表的每個行鏈表節點中
}
//注意矩陣的最後一行,若當前行不是第0行,則追加行節點
if (H.row) x.a.Append(H);
H.a.Zero(); //追加完成後,將頭節點first置0
return in;
}
4.3 輸出一個稀疏矩陣
//輸出一個稀疏矩陣
template <class T>
ostream& operator<<(ostream &out, const LinkedMatrix<T> &x)
{//把矩陣x送至輸出流
ChainIterator<HeadNode<T>> p; //頭節點遍歷器
//輸出矩陣的維數
out << "rows= " << x.rows << "columns= " << x.cols << endl;
//將h指向第一個頭節點
HeadNode<T> *h = p.Initial(x.a);
cout << "-----------------" << endl;
if (!h)
{
out << "沒有非零元素" << endl;
return out;
}
//每次輸出一行
while (h)
{
out << "row" << h->row << endl; //輸出行數
out << h->a << endl; //輸出鏈表
h = p.Next(); //指向下一個頭節點
}
return out;
}
4.4 求稀疏矩陣的轉置
//求稀疏矩陣的轉置,轉置當前矩陣,將結果放入b中
template <class T>
void LinkedMatrix<T>::Transpose(LinkedMatrix<T> &b) const
{
b.a.Erase(); //刪除b中所有節點
//創建用來收集b中各行元素的箱子
Chain<CNode<T>> *bin;
bin = new Chain<CNode<T>>[cols + 1];
//頭節點遍歷器
ChainIterator<HeadNode<T>> p;
//h指向*this的第一個頭節點
HeadNode<T> *h = p.Initial(a);
//把*this的元素複製到箱子中
while (h)
{
int r = h->row; //當前行數
//行鏈表遍歷器
ChainIterator<CNode<T>> q;
//z指向行鏈表的第一個節點
CNode<T> *z = q.Initial(h->a);
CNode<T> x; //臨時節點
//*this第r行中的元素變成b中第r列的元素
x.col = r;
//檢查*this中第r行的所有非零元素
while (z) //遍歷第r行
{
x.value = z->value; //取值
bin[z->col].Append(x); //添加節點
z = q.Next(); //指向下一個節點
}
h = p.Next(); //指向下一行
}
//設置b的維數
b.cols = rows;
b.rows = cols;
//b的頭節點鏈表
HeadNode<T> H;
//搜索箱子
for (int i = 1; i <= cols; i++)
{
if (!bin[i].IsEmpty()) //若轉置矩陣的第i行非空
{
H.row = i; //行數==列數
H.a = bin[i]; //頭節點==第i個行節點
b.a.Append(H); //在頭節點鏈表中添加節點H
bin[i].Zero(); //置表頭指針爲0
}
}
H.a.Zero(); //置表頭指針爲0
delete[] bin; //釋放箱子指針
}
5.測試
LinkedMatrix<int> M;
cin>>M; //輸入稀疏矩陣M
cout << M; //輸出稀疏矩陣M
LinkedMatrix<int> b; //聲明轉置後的矩陣
M.Transpose(b); //求矩陣M的轉置
cout << b; //輸出矩陣b
參考文獻:
[1] 數據結構算法與應用:C++描述(Data Structures, Algorithms and Applications in C++ 的中文版)