先简单介绍一下概念。设一个m行n列的矩阵具有n个值不等于零的元素,则称t/(m*n)为该矩阵的稀疏因子。通常称稀疏因子小于0.5的矩阵为稀疏矩阵(sparse matrix)。
由于零元素较多,再使用通常的2维数组表示会造成存储空间的浪费,同时在计算时所有零元也要参与运算,导致效率很低。所以有必要研究如何存储稀疏矩阵,即压缩存储。书上这里介绍了3种存储方式,其实第二种行链接的方式是顺序存储的改进方法,二者可以归到一起。由于前两种存储方式实现起来非常简单,书上讲得很细,这里就不啰嗦了,下面主要介绍第三种实现:十字链表。
十字链表是这样构成的:用链表模拟矩阵的行(或者列,这可以根据个人喜好来定),然后再构造代表列的链表,将每一行中的元素节点插入到对应的列中去。打个比方吧:这个十字链表的逻辑结构就像是一个围棋盘(没见过,你就想一下苍蝇拍,这个总见过吧),而非零元就好像是在棋盘上放的棋子,总共占的空间就是,确定那些线的表头节点和那些棋子代表的非零元节点。最后,我们用一个指针指向这个棋盘,这个指针就代表了这个稀疏矩阵。
现在,让我们看看非零元节点最少需要哪几个域,val是必须的,为了把线画下去还要有down、right,好像不需要别的了。但为了提高加法与乘法的效率,还要在每个节点增加两个值row,col分别表示节点所在的行、列。再看看表头节点,由于是链表的表头节点,所以就和后边的节点一样了。然后,行链表和列链表的表头节点实际上也各构成了一个链表。最后,我们只需要存储两个分别指向行表头与列表头节点的指针,就可以访问稀疏矩阵了。具体表示如下图:
为了能随机访问任意一行或一列,这里将头结点存放在一维数组中。下面是节点的定义:
class OLNode {
public:
int row,col,val;
OLNode* right;
OLNode* down;
OLNode(int i = 0, int j = 0, int v = 0,
OLNode* rgt = NULL, OLNode* dwn = NULL)
: row(i), col(j), val(v), right(rgt), down(dwn) {}
friend std::ostream& operator<<(std::ostream& os,
OLNode& eom)
{
os << "(" << eom.row << "," << eom.col
<< "," << eom.val << ")";
return os;
}
};
public:
int row,col,val;
OLNode* right;
OLNode* down;
OLNode(int i = 0, int j = 0, int v = 0,
OLNode* rgt = NULL, OLNode* dwn = NULL)
: row(i), col(j), val(v), right(rgt), down(dwn) {}
friend std::ostream& operator<<(std::ostream& os,
OLNode& eom)
{
os << "(" << eom.row << "," << eom.col
<< "," << eom.val << ")";
return os;
}
};
下边会依次介绍矩阵的转置、加法和乘法,但在这之前先来设计一下类的输出输出接口,一个好的接口能为接下来的运算提供许多方便。除了通常的重载>>与<<之外,为了乘法实现的简便还需要重载=,这在下面会看到:
class SMatrix_OL {
int maxRows, maxCols, total;
OLNode* pRowHead;
OLNode* pColHead;
void initialize() {
maxRows = maxCols = total = 0;
pRowHead = pColHead = NULL;
}
void clear();
void copy(const SMatrix_OL& sm);
friend const SMatrix_OL
vectorMultiply(OLNode* colVector, OLNode* rowVector,
int rows, int cols);
public:
SMatrix_OL() { initialize(); }
//初始化一个i行j列的零矩阵
SMatrix_OL(int i, int j) {
require(i > 0 && j > 0,
"SMatrix_OL::SMatrix illegal matrix!");
maxRows = i;
maxCols = j;
total = 0;
pRowHead = new OLNode[maxRows + 1];
pColHead = new OLNode[maxCols + 1];
}
SMatrix_OL(const SMatrix_OL& sm) {
copy(sm);
}
SMatrix_OL& operator=(const SMatrix_OL& sm) {
if (maxRows) clear();
copy(sm);
return *this;
}
~SMatrix_OL() {
clear();
}
friend std::ostream& operator<<(std::ostream& os,
const SMatrix_OL& sm) {
for (int i = 1; i <= sm.maxRows; i++) {
OLNode* p = sm.pRowHead[i].right;
if (i == 1)
os << "/";
else if (i == sm.maxRows)
os << "/";
else os << "|";
for (int j = 1; j <= sm.maxCols; j++) {
int val;
if (p == NULL || p->col > j)
val = 0;
else {
val = p->val;
p = p->right;
}
os.setf(std::ios::right, std::ios::adjustfield);
os.width(4);
os << val;
}
os.setf(std::ios::right, std::ios::adjustfield);
os.width(4);
if (i == 1)
os << "/";
else if (i == sm.maxRows)
os << "/";
else os << "|";
os << std::endl;
}
return os;
}
friend std::istream& operator>>(std::istream& is,
SMatrix_OL& sm) {
cout << "创建稀疏矩阵:" << endl;
cout << "输入行数、列数和非零元个数:" << endl;
is >> sm.maxRows >> sm.maxCols >> sm.total;
require(sm.maxRows > 0 && sm.maxCols > 0 && sm.total >= 0,
"SMatrix_OL::operator>> illegal input!");
sm.pRowHead = new OLNode[sm.maxRows + 1];
sm.pColHead = new OLNode[sm.maxCols + 1];
for (int n = 1; n <= sm.total; n++) {
cout << "输入第" << n << "个非零元:" << endl;
int i, j, val;
is >> i >> j >> val;
require(i > 0 && j > 0,
"SMatrix_OL::operator>> illegal input!");
OLNode* pNew = new OLNode(i, j, val);
OLNode* prior = &sm.pRowHead[i];
while (prior->right != NULL && prior->right->col < j)
prior = prior->right;
pNew->right = prior->right;
prior->right = pNew;//行链接完毕
prior = &sm.pColHead[j];
while (prior->down != NULL && prior->down->row < i)
prior = prior->down;
pNew->down = prior->down;
prior->down = pNew;//列链接完毕
}
cout << "稀疏矩阵创建完毕!" << endl;
return is;
}
};
void SMatrix_OL::clear() {
for (int i = 1; i <= maxRows; i++) {
if (pRowHead[i].right == NULL) continue;
OLNode* p = pRowHead[i].right;
//删除当前行的所有结点
while (p != NULL) {
OLNode* q = p;
p = p->right;
delete q;
}
}
delete []pRowHead;
delete []pColHead;
}
void SMatrix_OL::copy(const SMatrix_OL& sm) {
maxCols = sm.maxCols;
maxRows = sm.maxRows;
total = sm.total;
pRowHead = new OLNode[maxRows + 1];
pColHead = new OLNode[maxCols + 1];
OLNode** pUpNode = new OLNode*[maxCols + 1];
for (int n = 1; n <= maxCols; n++)
pUpNode[n] = &pColHead[n];
for (int i = 1; i <= maxRows; i++) {
if (sm.pRowHead[i].right == NULL) continue;
OLNode* prior = &pRowHead[i];
OLNode* pOld = sm.pRowHead[i].right;
while (pOld != NULL) {
OLNode* pCopy = new
OLNode(i, pOld->col, pOld->val);
prior = prior->right = pCopy;
pUpNode[pOld->col] =
pUpNode[pOld->col]->down = pCopy;
pOld = pOld->right;
}
}
}
int maxRows, maxCols, total;
OLNode* pRowHead;
OLNode* pColHead;
void initialize() {
maxRows = maxCols = total = 0;
pRowHead = pColHead = NULL;
}
void clear();
void copy(const SMatrix_OL& sm);
friend const SMatrix_OL
vectorMultiply(OLNode* colVector, OLNode* rowVector,
int rows, int cols);
public:
SMatrix_OL() { initialize(); }
//初始化一个i行j列的零矩阵
SMatrix_OL(int i, int j) {
require(i > 0 && j > 0,
"SMatrix_OL::SMatrix illegal matrix!");
maxRows = i;
maxCols = j;
total = 0;
pRowHead = new OLNode[maxRows + 1];
pColHead = new OLNode[maxCols + 1];
}
SMatrix_OL(const SMatrix_OL& sm) {
copy(sm);
}
SMatrix_OL& operator=(const SMatrix_OL& sm) {
if (maxRows) clear();
copy(sm);
return *this;
}
~SMatrix_OL() {
clear();
}
friend std::ostream& operator<<(std::ostream& os,
const SMatrix_OL& sm) {
for (int i = 1; i <= sm.maxRows; i++) {
OLNode* p = sm.pRowHead[i].right;
if (i == 1)
os << "/";
else if (i == sm.maxRows)
os << "/";
else os << "|";
for (int j = 1; j <= sm.maxCols; j++) {
int val;
if (p == NULL || p->col > j)
val = 0;
else {
val = p->val;
p = p->right;
}
os.setf(std::ios::right, std::ios::adjustfield);
os.width(4);
os << val;
}
os.setf(std::ios::right, std::ios::adjustfield);
os.width(4);
if (i == 1)
os << "/";
else if (i == sm.maxRows)
os << "/";
else os << "|";
os << std::endl;
}
return os;
}
friend std::istream& operator>>(std::istream& is,
SMatrix_OL& sm) {
cout << "创建稀疏矩阵:" << endl;
cout << "输入行数、列数和非零元个数:" << endl;
is >> sm.maxRows >> sm.maxCols >> sm.total;
require(sm.maxRows > 0 && sm.maxCols > 0 && sm.total >= 0,
"SMatrix_OL::operator>> illegal input!");
sm.pRowHead = new OLNode[sm.maxRows + 1];
sm.pColHead = new OLNode[sm.maxCols + 1];
for (int n = 1; n <= sm.total; n++) {
cout << "输入第" << n << "个非零元:" << endl;
int i, j, val;
is >> i >> j >> val;
require(i > 0 && j > 0,
"SMatrix_OL::operator>> illegal input!");
OLNode* pNew = new OLNode(i, j, val);
OLNode* prior = &sm.pRowHead[i];
while (prior->right != NULL && prior->right->col < j)
prior = prior->right;
pNew->right = prior->right;
prior->right = pNew;//行链接完毕
prior = &sm.pColHead[j];
while (prior->down != NULL && prior->down->row < i)
prior = prior->down;
pNew->down = prior->down;
prior->down = pNew;//列链接完毕
}
cout << "稀疏矩阵创建完毕!" << endl;
return is;
}
};
void SMatrix_OL::clear() {
for (int i = 1; i <= maxRows; i++) {
if (pRowHead[i].right == NULL) continue;
OLNode* p = pRowHead[i].right;
//删除当前行的所有结点
while (p != NULL) {
OLNode* q = p;
p = p->right;
delete q;
}
}
delete []pRowHead;
delete []pColHead;
}
void SMatrix_OL::copy(const SMatrix_OL& sm) {
maxCols = sm.maxCols;
maxRows = sm.maxRows;
total = sm.total;
pRowHead = new OLNode[maxRows + 1];
pColHead = new OLNode[maxCols + 1];
OLNode** pUpNode = new OLNode*[maxCols + 1];
for (int n = 1; n <= maxCols; n++)
pUpNode[n] = &pColHead[n];
for (int i = 1; i <= maxRows; i++) {
if (sm.pRowHead[i].right == NULL) continue;
OLNode* prior = &pRowHead[i];
OLNode* pOld = sm.pRowHead[i].right;
while (pOld != NULL) {
OLNode* pCopy = new
OLNode(i, pOld->col, pOld->val);
prior = prior->right = pCopy;
pUpNode[pOld->col] =
pUpNode[pOld->col]->down = pCopy;
pOld = pOld->right;
}
}
}
这些都是很常规的实现,不再细说了。首先是转置运算,如果按照一般的方法需要在转置的同时改动很多指针的指向,比较麻烦,这里使用的方式是先把矩阵复制一个临时副本,然后清除原来的矩阵,再以列序从副本复制回来,则现在的矩阵就是原来的转置。代码如下:
//先把自己复制给sm,然后把自己构造成sm的转置
void Transpose() {
SMatrix_OL sm = *this;
clear();
maxRows = sm.maxCols;
maxCols = sm.maxRows;
total = sm.total;
pRowHead = new OLNode[maxRows + 1];
pColHead = new OLNode[maxCols + 1];
OLNode** pUpNode = new OLNode*[maxCols + 1];
for (int n = 1; n <= maxCols; n++)
pUpNode[n] = &pColHead[n];
for (int j = 1; j <= sm.maxCols; j++) {
if (sm.pColHead[j].down == NULL) continue;
OLNode* prior = &pRowHead[j];
OLNode* pOld = sm.pColHead[j].down;
while (pOld != NULL) {
OLNode* pNew = new OLNode(j, pOld->row, pOld->val);
prior = prior->right = pNew;
pUpNode[pNew->col] =
pUpNode[pNew->col]->down = pNew;
pOld = pOld->down;
}
}
}
const SMatrix_OL operator~() {
SMatrix_OL sm = *this;
sm.Transpose();
return SMatrix_OL(sm);
}
friend const SMatrix_OL
operator+(const SMatrix_OL& smA, const SMatrix_OL& smB) {
require(smA.maxRows == smB.maxRows &&
smA.maxCols == smB.maxCols,
"SMatrix_OL::operator+ cannot add!");
SMatrix_OL smC = smA;
smC.Add(smB);
return SMatrix_OL(smC);
}
void Transpose() {
SMatrix_OL sm = *this;
clear();
maxRows = sm.maxCols;
maxCols = sm.maxRows;
total = sm.total;
pRowHead = new OLNode[maxRows + 1];
pColHead = new OLNode[maxCols + 1];
OLNode** pUpNode = new OLNode*[maxCols + 1];
for (int n = 1; n <= maxCols; n++)
pUpNode[n] = &pColHead[n];
for (int j = 1; j <= sm.maxCols; j++) {
if (sm.pColHead[j].down == NULL) continue;
OLNode* prior = &pRowHead[j];
OLNode* pOld = sm.pColHead[j].down;
while (pOld != NULL) {
OLNode* pNew = new OLNode(j, pOld->row, pOld->val);
prior = prior->right = pNew;
pUpNode[pNew->col] =
pUpNode[pNew->col]->down = pNew;
pOld = pOld->down;
}
}
}
const SMatrix_OL operator~() {
SMatrix_OL sm = *this;
sm.Transpose();
return SMatrix_OL(sm);
}
friend const SMatrix_OL
operator+(const SMatrix_OL& smA, const SMatrix_OL& smB) {
require(smA.maxRows == smB.maxRows &&
smA.maxCols == smB.maxCols,
"SMatrix_OL::operator+ cannot add!");
SMatrix_OL smC = smA;
smC.Add(smB);
return SMatrix_OL(smC);
}
OK,该本篇文章的压轴算法出场了^_^那就是乘法,书上根本就没有提到十字链表的乘法,原因就是太麻烦了。你想啊,每个节点有两个指针域,而且在做乘法运算时一个矩阵是按行访问,另一个是按列访问,乘积矩阵中的每一个元素都是一行与一列的乘积。我做到这里时头都大了,于是我又想能不能换一种方式:如果矩阵A是行矢量,矩阵B是列矢量,那么A*B很好实现。而一个普通的矩阵可以分解成由子矩阵组成的矢量,所以把A变成行矢量,把B变成列矢量,那么乘积矩阵可由n个子矩阵相加组成,其中n等于A的列数(或B的行数)。见下图:
这样一来,乘法就容易实现了:
friend const SMatrix_OL
operator*(const SMatrix_OL& smA, const SMatrix_OL& smB) {
require(smA.maxCols == smB.maxRows,
"SMatrix_OL operator* cannot mult!");
SMatrix_OL smC(smA.maxRows, smB.maxCols);
//smC = A1*B1+A2*B2+...+An*Bn,n=smA.maxCols=smB.maxRows
for (int n = 1; n <= smA.maxCols; n++)
smC.Add(
vectorMultiply(
smA.pColHead[n].down,
smB.pRowHead[n].right,
smA.maxRows, smB.maxCols));
return SMatrix_OL(smC);
}
operator*(const SMatrix_OL& smA, const SMatrix_OL& smB) {
require(smA.maxCols == smB.maxRows,
"SMatrix_OL operator* cannot mult!");
SMatrix_OL smC(smA.maxRows, smB.maxCols);
//smC = A1*B1+A2*B2+...+An*Bn,n=smA.maxCols=smB.maxRows
for (int n = 1; n <= smA.maxCols; n++)
smC.Add(
vectorMultiply(
smA.pColHead[n].down,
smB.pRowHead[n].right,
smA.maxRows, smB.maxCols));
return SMatrix_OL(smC);
}
其中的核心就是私有成员函数vectorMultiply,将两个矢量相乘,其实现也很简单:
const SMatrix_OL
vectorMultiply(OLNode* colVector,
OLNode* rowVector, int rows, int cols)
{
SMatrix_OL sm(rows, cols);
if (colVector != NULL && rowVector != NULL) {
OLNode** pUpNode = new OLNode*[sm.maxCols + 1];
for (int n = 1; n <= sm.maxCols; n++)
pUpNode[n] = &sm.pColHead[n];
//行、列矢量相乘
for (; colVector != NULL; colVector = colVector->down) {
OLNode* prior = &sm.pRowHead[colVector->row];
for (OLNode* pCur = rowVector;
pCur != NULL; pCur = pCur->right)
{
OLNode* pNew = new
OLNode(colVector->row,
pCur->col, colVector->val * pCur->val);
prior = prior->right = pNew;
pUpNode[pNew->col] =
pUpNode[pNew->col]->down = pNew;
sm.total++;
}
}
}
return SMatrix_OL(sm);
}
vectorMultiply(OLNode* colVector,
OLNode* rowVector, int rows, int cols)
{
SMatrix_OL sm(rows, cols);
if (colVector != NULL && rowVector != NULL) {
OLNode** pUpNode = new OLNode*[sm.maxCols + 1];
for (int n = 1; n <= sm.maxCols; n++)
pUpNode[n] = &sm.pColHead[n];
//行、列矢量相乘
for (; colVector != NULL; colVector = colVector->down) {
OLNode* prior = &sm.pRowHead[colVector->row];
for (OLNode* pCur = rowVector;
pCur != NULL; pCur = pCur->right)
{
OLNode* pNew = new
OLNode(colVector->row,
pCur->col, colVector->val * pCur->val);
prior = prior->right = pNew;
pUpNode[pNew->col] =
pUpNode[pNew->col]->down = pNew;
sm.total++;
}
}
}
return SMatrix_OL(sm);
}
下面该测试了,这里同样使用了sstream文件,其功能在上一篇文章已经说过了。
#include "SMatrix.h"
#include <iostream>
#include <sstream>
using namespace std;
int main() {
stringstream input("
3 2 6
1 1 1 1 2 2 2 1 3 2 2 4 3 1 5 3 2 6
");
SMatrix_OL A;
input >> A;
cout << "A =" << endl << A;
SMatrix_OL B = A;
cout << "B =" << endl << B;
cout << "A + B =" << endl << A + B;
cout << "B's transpose matrix = " << endl << ~B;
cout << "A * ~B =" << endl << A * ~B;
return 0;
}
#include <iostream>
#include <sstream>
using namespace std;
int main() {
stringstream input("
3 2 6
1 1 1 1 2 2 2 1 3 2 2 4 3 1 5 3 2 6
");
SMatrix_OL A;
input >> A;
cout << "A =" << endl << A;
SMatrix_OL B = A;
cout << "B =" << endl << B;
cout << "A + B =" << endl << A + B;
cout << "B's transpose matrix = " << endl << ~B;
cout << "A * ~B =" << endl << A * ~B;
return 0;
}
可以看出,重载了一些符号之后使得矩阵的运算与内部类型就很相似了,下面是运行结果:
好了,第五章到此就结束了。本来后面还有广义表,但由于最近时间比较紧张就不打算再写了。这篇文章也是很匆忙地写完的,总觉得有些仓促,以后有时间会再修改一下,毕竟十字链表不比前几章学过的线性表,在实现上要复杂一些,包括下一节的广义表也是。虽然这部分内容不是重点,但我觉得系统地学习一下还是有好处的,可以为后面学习树打下基础,因为树的大部分运算都是递归的,并且其中的二叉树与十字链表一样有两个指针域。下一篇开始介绍第六章--树,也就是数据结构这门课程最重要的章节~预计分3篇文章,如果时间足够的话可能会把广义表补上,但广义表我又是在想不出一个好的应用,只写基本操作也没什么意思,到时候再说吧,呵呵~
最后说几句题外话,最近忙主要是因为把精力都花在学外语上了。这个星期是我们学校的英语周,从美国来了10多个老外,每天晚上都有讲座或者是娱乐活动。我每天下午都会去找他们聊天,练习口语,感觉这个星期收获很大,刚开始的时候只能一个词一个词的往外蹦,现在进本上能一段一段的说了!并且与一个叫Christ的老头儿交上了朋友,今天我去送得他,还送给他一个自己做得小礼物。老外很有意思,他们和你说话时面部表情特别丰富,还是不是加上许多肢体动作,让人感觉很亲切,呵呵~