在矩阵中,有一类很重要的矩阵,就是-----稀疏矩阵。


所谓的稀疏矩阵呢,就是指的是,在矩阵中,有效的数据个数远远小于无效的数据个数(并且这些数据排列顺序没有规律)。我们下面先举个稀疏矩阵的例子:

wKiom1cNoXqgYIYUAAAPLNUT7Mw525.png

有效数据个数仅仅6个,其余都为无效数据0.


那我们将稀疏矩阵存在压缩矩阵中,设定一个三元组,使用{row,col,value}存储每一个有效数据,三元组按原矩阵中的位置,以行优先级先后顺序依次存放。

我们建立一个结构体:

struct Triple//定义一个三元组,用来存储稀疏矩阵的x,y,坐标值
{
    int _row;
    int _col;
    T _value;
};

将每一个有效数据(三元组)存在顺序表vector中,打印数据就按照顺序表的特点打印。

矩阵的转置:


wKiom1cNoyLgdIoCAAAda7pd8GI613.png

所以,转置就是将原矩阵的行、列对换,也就是将[i][j]和[j][i]位置上的数据对换。


代码如下:


#include<vector>

template<class T>
struct Triple//定义一个三元组,用来存储稀疏矩阵的x,y,坐标值
{
    int _row;
    int _col;
    T _value;

    Triple(int row, int col, int value)
        :_row(row)
        , _col(col)
        , _value(value)
    {}
};



template<class T>
class SparseMatrix
{
public:
    SparseMatrix(T* a, int m, int n, const T& invalid)
        
    {
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (invalid != a[i*n+j])
                {
                         //将每一个有效数据(三元组)存在顺序表vector中
                    Triple <T> tmp(i, j, a[i*n + j]);
                    
                    _a.push_back(tmp);                    
                }
                
            }
        }    
    }


    //用坐标形式打印稀疏矩阵
    void Display(int m, int n, const T& invalid)
    {
        cout << "用坐标形式打印稀疏矩阵" << endl;
        cout <<"{" <<"x," << "  " << "y," << "  " << "value" <<"}" <<endl;
        for (int k = 0; k < _a.size(); k++)
        {
  
          cout << "{" << _a[k]._row << ",  " << 
_a[k]._col << ",  " << _a[k]._value << "  " << 
"}" << endl;
        }                                
    }

    //用矩阵形式打印稀疏矩阵
    void DisplayMatrix(int m, int n, const T& invalid)
    {        
        cout << "用矩阵形式打印稀疏矩阵" << endl;
        int k = 0;
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {        
                
                if (k<_a.size()&&_a[k]._row == i && _a[k]._col == j)
                {
                    cout << _a[k]._value << "  ";
                    k++;
                }
                else
                {
                    cout << invalid << "  ";
                }                                
            }
            cout << endl;
        }
    }
    
    SparseMatrix<T> Transport(T* a, int m, int n, const T& invalid);

protected:
    vector <Triple <T>> _a;
    
};



void Test()
{

    int a[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 },
                  }; 

    int m = 6;
    int n = 5;
    SparseMatrix<int> sm((int*)a, m, n, 0);
    sm.Display( m, n, 0);
    sm.DisplayMatrix( m, n, 0);
    SparseMatrix<int> sm1((int*)a, m, n, 0);
    sm1 = sm.Transport((int*)a, m, n, 0);

    
    sm1.Display( n, m, 0);
    sm1.DisplayMatrix(n, m, 0);
}

int main()
{
    Test();
    system("pause");
    return 0;
}

下面我们单独说说如何实现矩阵转置的代码:


现在我们知道,此时的矩阵不再是几乘几,而是以一个三元组Triple形式存储的。那我们就没有办法像线性代数中将矩阵的第一行放在新矩阵的第一列,以此重复。


而是换一种思路来想这个问题:

(1)转置后的矩阵第一列是由之前矩阵的第一行得来的。

(2)之前是行优先存储,现在要用列优先存储。


那我们现在想,是不是可以遍历矩阵的第0列分别拿该列的元素去与三元组中的元素比较是否满足是第0列。即:拿第0列元素,去与三元组比较,发现1,2是第0列。则把1,2按顺序存放在新的三元组中。再第一列,第二列,……时间复杂度:O(列数*有效数据个数)。


我们现在看下代码:

template<class T>
SparseMatrix<T> SparseMatrix<T>::Transport(T* a, int m, int n, const T& invalid)
{
    cout << "矩阵转置:" << endl;
    SparseMatrix<T> ret;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < _a.size(); j++)
        {
            if (_a[j]._col == i)
            {
                Triple<T> tri(_a[j]._col, _a[j]._row, _a[j]._value);
                ret._a.push_back(tri);
            }
        }
    }
    return ret;
}


快速转置:

思想是统计好转置后上一行的元素个数RowCounts,及转置后每行在压缩矩阵的起始位置RowStarts,由此确定下一行元素位置。

template<class T>
SparseMatrix<T> SparseMatrix<T>::FastTransport(int m, int n, const T& invalid)
{
    SparseMatrix<T> ret;
    ret._a.resize(_a.size());
        
    vector<int> RowCounts;     //转置后每行元素的个数
    RowCounts.resize(n);
    RowCounts.assign(n, 0);
    for (int i = 0; i < _a.size(); i++)
    {
        RowCounts[_a[i]._col]++;    //元素相同列的话,列号对应的顺序表加加
    }
        
    vector<int> RowStarts;
    RowStarts.resize(n);
    RowStarts.assign(n, 0);
    
    //下一行元素的起始位置等于上一行中的元素个数与上一行元素的起始位置之和
    for (int j = 0; j < n-1; j++) 
    {
        RowStarts[j + 1] = RowStarts[j] + RowCounts[j];
    }
     
    //行列数互换
    for (int k = 0; i < _a.size(); k++)
    {
        ret._a[RowStarts[_a[k]._col]]._col = _a[k]._row;
        ret._a[RowStarts[_a[k]._col]]._row = _a[k]._col;
        ret._a[RowStarts[_a[k]._col]++]._value = _a[k]._value;
    }
    return ret;
}

则:时间复杂度为:

2*元素个数+列数

即:O(元素个数+列数)