数组与矩阵

150 阅读10分钟

数组

映射

  1. 行主映射: 先存储行号较小的, 行号相同, 先存储列号较小的
  2. 列主映射: 先存储列号较小的

image.png

不规则二维数组

即一个二维数组其每一行的元素个数不一样

#include<iostream>
using namespace std;

int main() {
    // 一个二级指针, 表示有 5 行的二维数组
    int** irregularArray = new int* [5];

    // 给每一行分配一个数组, 长度不一
    int len[5] = { 4, 3, 2, 5, 6 };
    for (int i = 0; i < 5; i++) {
        irregularArray[i] = new int[len[i]];
    }

    // 像普通二维数组一样使用
    irregularArray[1][0] = 1;
    cout << irregularArray[1][0];
    
    return 0;
}

自定义一维数组

解决普通数组下标越界问题

构造函数
template<class T>
Array1D<T>::Array1D(int size) {
    if (size < 0) throw "数组初始化错误";
    this->size = size;
    element = new T[size];
}
拷贝构造

这里要使用 (const xx&) 否则后面使用 = 时可能会出现 Class has no suitable copy constructor 的报错

template<class T>
Array1D<T>::Array1D(const Array1D<T>& v) {
    size = v.Size();
    element = new T[size];
    for (int i = 0; i < size; i++) {
        element[i] = v[i];
    }
}
实现 [] 访问
template<class T>
T& Array1D<T>::operator[](int i) const {
    if (i < 0 || i >= size) throw "下标越界";
    return element[i];
}
重载 =

为支持连等而返回引用, 同理这里也要使用 const

template<class T>
Array1D<T>& Array1D<T>::operator=(const Array1D<T>& v) {
    if (&v == this) {
        return *this;
    }
    if (element != NULL) {
        delete[] element;
        size = 0;
    }

    size = v.Size();
    element = new int[size];
    for (int i = 0; i < size; i++) {
        element[i] = v[i]; // 这里重载了 []
    }
    return *this;
}
重载 ==
template<class T>
bool Array1D<T>::operator==(const Array1D<T>& v) {
    if (size != v.Size()) { // 判断长度是否相等
        return false;
    }

    // 判断每个元素是否相等
    for (int i = 0; i < size; i++) {
        if (element[i] != v[i]) {
            return false;
        }
    }
    return true;
}
🎉 完整代码
#include<iostream>
using namespace std;

template<class T>
class Array1D {
    
public:
    // 这个具体实现要写在里面, 不能写在外面
    friend ostream& operator << (ostream&os, Array1D<T>& v) {
        os << '[';
        for (int i = 0; i < v.Size() - 1; i++) {
            os << v[i] << ',';
        }
        os << v[v.Size() - 1] << ']';
        return os;
    };
    Array1D(int size = 0);
    Array1D(const Array1D<T>& v);
    ~Array1D() { delete[] element; }
    T& operator[](int i) const;
    int Size() const { return size; }
    void setData(int index, T val) {
        element[index] = val;
    };
    Array1D<T>& operator = (const Array1D<T>& v);
    bool operator == (const Array1D<T>& v);
    void ReSize(int sz);
    Array1D<T> operator - (const Array1D<T>& v) const;
private:
    int size;
    T* element; // 一维数组
};

// 构造函数
template<class T>
Array1D<T>::Array1D(int size) {
    if (size < 0) throw "数组初始化错误";
    this->size = size;
    element = new T[size];
}

// 拷贝构造函数
template<class T>
Array1D<T>::Array1D(const Array1D<T>& v) {
    size = v.Size();
    element = new T[size];
    for (int i = 0; i < size; i++) {
        element[i] = v[i];
    }
}

template<class T>
T& Array1D<T>::operator [](int i) const {
    if (i < 0 || i >= size) throw "下标越界";
    return element[i];
}

// 为支持连等而返回引用
template<class T>
Array1D<T>& Array1D<T>::operator=(const Array1D<T>& v) {
    if (&v == this) {
        return *this;
    }
    if (element != NULL) {
        delete[] element;
        size = 0;
    }

    size = v.Size();
    element = new int[size];
    for (int i = 0; i < size; i++) {
        element[i] = v[i]; // 这里重载了 []
    }
    return *this;
}

template<class T>
bool Array1D<T>::operator ==(const Array1D<T>& v) {
    if (size != v.Size()) { // 判断长度是否相等
        return false;
    }

    // 判断每个元素是否相等
    for (int i = 0; i < size; i++) {
        if (element[i] != v[i]) {
            return false;
        }
    }
    return true;
}

template<class T>
void Array1D<T>::ReSize(int sz) {
    delete[] element;
    element = new T[sz];
    size = sz;
}

template<class T>
Array1D<T> Array1D<T>::operator-(const Array1D<T>& v) const {
    if (size != v.Size()) throw "无法相减";
    Array1D<T> a(size);
    for (int i = 0; i < size; i++) {
        a[i] = element[i] - v[i];
    }
    return a;
}

int main() {
    Array1D<int> a1(3);
    // 也可以 a1[0] = 1;
    a1.setData(0, 1);
    a1.setData(1, 2);
    a1.setData(2, 3);

    // 测试 []
    for (int i = 0; i < a1.Size(); i++) {
        cout << a1[i] << " "; // 1 2 3
    }

    // 测试复制构造函数
    Array1D<int> a2(a1);
    cout << a2[1] << endl; // 2

    // 测试 =
    // 这里不能使用 Array1D<int> a3 = a2;
    // 因为这会调用复制构造函数
    Array1D<int> a3;
    a3 = a2;
    cout << a3[1] << endl; // 2

    // 测试 ==
    cout << (a3 == a2) << endl; // 1
    
    return 0;
}

自定义二维数组

在 Array1D 的基础上实现 Array2D

🌟完整代码
// 忽略 Array1D 代码...

template<class T>
class Array2D {
public:
    int Rows() const { return rows; }
    int Columns() const { return cols; }
    Array2D<T>(int, int);
    Array2D(const Array2D<T>& m);
    ~Array2D() {
        delete[] row;
    }
    Array1D<T>& operator[](int i) const;
    Array2D<T> operator - (const Array2D<T>& m) const;
    friend ostream& operator << (ostream& os, const Array2D<T>& v) {
        for (int i = 0; i < v.Rows(); i++) {
            os << v[i] << endl;
        }
        return os;
    };
private:
    int rows, cols;
    Array1D<T>* row;
};

// 构造函数
template<class T>
Array2D<T>::Array2D(int r, int c) {
    if (r <= 0 && c <= 0) throw "数组初始化错误";

    rows = r;
    cols = c;

    row = new Array1D<T>[r];

    for (int i = 0; i < r; i++) {
        row[i].ReSize(c);
    }
}

// 拷贝构造函数
template<class T>
Array2D<T>::Array2D(const Array2D<T>& m) {
    rows = m.rows;
    cols = m.cols;
    row = new Array1D<T>[rows];
    for (int i = 0; i < rows; i++) {
        // 一维数组已经重载了 =
        // 这里可以直接使用 =
        row[i] = m.row[i];
    }
}

// 重载 []
template<class T>
Array1D<T>& Array2D<T>::operator[](int i) const {
    if (i < 0 || i >= rows) throw "下标越界";
    return row[i];
}

// 重载 -
template<class T>
Array2D<T> Array2D<T>::operator - (const Array2D<T>& m) const {
    if (rows != m.rows || cols != m.cols) throw "数组大小不匹配";
    
    Array2D<T> w(rows, cols);
    for (int i = 0; i < rows; i++) {
        w.row[i] = row[i] - m.row[i];
    }
    return w;
}


int main() {
    Array2D<int> a(3, 3);
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            a[i][j] = j;
        }
    }

    cout << a << endl;
    // [0, 1, 2]
    // [0, 1, 2]
    // [0, 1, 2]

    Array2D<int> a2 = a;
    Array2D<int> a3 = a2 - a;
    cout << a3;

    // [0, 0, 0]
    // [0, 0, 0]
    // [0, 0, 0]
    
    return 0;
}

矩阵

与二维数组不同在于, 矩阵的行列索引应该从 1 开始

构造函数

生成一个 r×c 的矩阵或者 0×0 的

template<class T>
Matrix<T>::Matrix(int r, int c) {
    if (r < 0 || c < 0) throw "行列必须非负";
    if ((!r || !c) && (r || c)) throw "要么都是0, 要么都不等于0";

    rows = r; cols = c;
    element = new T[r * c];
}

复制构造函数

template<class T>
Matrix<T>::Matrix(const Matrix<T>& m) {
    rows = m.Rows();
    cols = m.Columns();
    element = new T[rows * cols];
    // 复制元素到 element
    copy(m.element, m.element + rows * cols, element);
}

重载 =

template<class T>
Matrix<T>& Matrix<T>::operator=(const Matrix<T>& m) {
    if (this != &m) { // 不能自己复制自己
        delete[] element;
        rows = m.Rows();
        cols = m.Columns();
        element = new T[rows * cols];
        copy(m.element, m.element + rows * cols, element);
   }
    return *this;
}

() 操作符

template<class T>
T& Matrix<T>::operator()(int i, int j) const {
    if (i < 1 || i > rows || j < 1 || j > cols) {
        throw "错误索引!";
    }
    // 使用行主次序将一维数组映射到一维数组中
    return element[(i - 1) * cols + j - 1];
}

矩阵乘法

时间复杂度 O(rows×cols×m.Coloums())O(rows×cols×m.Coloums())

template<class T>
Matrix<T> Matrix<T>::operator*(const Matrix<T>& m) const {
    if (cols != m.Rows()) {
       throw "矩阵右乘要求左矩阵列数与右矩阵行数相等!";
    }

    // m×n 矩阵与 a×b 矩阵相乘, 结果为 m×b
    Matrix<T> w(rows, m.Columns());
    int ct = 0, cm = 0, cw = 0;

    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= m.Columns(); j++) {
            T sum = element[ct] * m.element[cm];
            for (int k = 2; k <= cols; k++) {
                ct++;
                cm += m.Columns();
                sum += element[ct] * m.element[cm];
            }
            w.element[cw++] = sum;
            ct -= cols - 1;
            cm = j;
        }
        ct += cols;
        cm = 0;
    }
    return w;
}
🐼 完整代码
#include<iostream>
using namespace std;

template<class T>
class Matrix {
public:
    friend ostream& operator << (ostream&os, const Matrix<T>&m) {
        for (int i = 1; i <= m.Rows(); i++) {
            for (int j = 1; j <= m.Columns(); j++) {
                os << m(i,j) << " ";
            }
            os << "\n";
        }
        return os;
    };
    Matrix(int r = 0, int c = 0);
    Matrix(const Matrix<T>& m); // copy constructor
    ~Matrix() { delete[] element; }
    int Rows() const { return rows; }
    int Columns() const { return cols; }
    T& operator()(int i, int j) const;
    Matrix<T>& operator=(const Matrix<T>& m);
    Matrix<T> operator+(const Matrix<T>& m) const;
    Matrix<T> operator-(const Matrix<T>& m) const;
    Matrix<T> operator*(const Matrix<T>& m) const;
private:
    int rows, cols;
    T* element;
};

// 构造函数
template<class T>
Matrix<T>::Matrix(int r, int c) {
    if (r < 0 || c < 0) throw "行列必须非负";
    if ((!r || !c) && (r || c)) {
        throw "要么都是0, 要么都不等于0";
    }

    rows = r; cols = c;
    element = new T[r * c];
}

// 复制构造函数
template<class T>
Matrix<T>::Matrix(const Matrix<T>& m) {
    rows = m.Rows();
    cols = m.Columns();
    element = new T[rows * cols];
    // 复制元素到 element
    copy(m.element, m.element + rows * cols, element);
}

// () 操作符
template<class T>
T& Matrix<T>::operator()(int i, int j) const {
    if (i < 1 || i > rows || j < 1 || j > cols) {
        throw "错误索引!";
    }
    // 使用行主次序将一维数组映射到一维数组中
    return element[(i - 1) * cols + j - 1];
    // |   1      2      3    4   ...   cols |
    // | cols+1 cols+2  ...                  |
}

// 重载 =
template<class T>
Matrix<T>& Matrix<T>::operator=(const Matrix<T>& m) {
    if (this != &m) { // 不能自己复制自己
        delete[] element;
        rows = m.Rows();
        cols = m.Columns();
        element = new T[rows * cols];
        copy(m.element, m.element + rows * cols, element);
    }
    return *this;
}

// 矩阵加法
template<class T>
Matrix<T> Matrix<T>::operator+(const Matrix<T>& m) const {
    if (rows != m.Rows() || cols != m.Columns()) {
        throw "非同型矩阵无法相加!";
    }

    Matrix<T> w(rows, cols);
    // 矩阵加法, 就是对应位置元素相加
    for (int i = 0; i < rows * cols; i++) {
        w.element[i] = element[i] + m.element[i];
    }

    return w;
}

template<class T>
Matrix<T> Matrix<T>::operator-(const Matrix<T>& m) const {
    if (rows != m.Rows() || cols != m.Columns()) {
        throw "非同型矩阵无法相减!";
    }

    Matrix<T> w(rows, cols);
    // 矩阵减法, 就是对应位置元素相减
    for (int i = 0; i < rows * cols; i++) {
        w.element[i] = element[i] - m.element[i];
    }

    return w;
}

// 矩阵乘法
template<class T>
Matrix<T> Matrix<T>::operator*(const Matrix<T>& m) const {
    if (cols != m.Rows()) {
        throw "矩阵右乘要求左矩阵列数与右矩阵行数相等!";
    }

    // m×n 矩阵与 a×b 矩阵相乘, 结果为 m×b
    Matrix<T> w(rows, m.Columns());
    int ct = 0, cm = 0, cw = 0;

    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= m.Columns(); j++) {
            T sum = element[ct] * m.element[cm];
            for (int k = 2; k <= cols; k++) {
                ct++;
                cm += m.Columns();
                sum += element[ct] * m.element[cm];
            }
            w.element[cw++] = sum;
            ct -= cols - 1;
            cm = j;
        }
        ct += cols;
        cm = 0;
    }
    return w;
}


int main() {
    Matrix<int> m(10, 10);

    // 这里下标从 1 开始
    for (int i = 1; i <= 10; i++) {
        for (int j = 1; j <= 10; j++) {
            m(i, j) = j;
        }
    }

    cout << m << endl << endl;

    Matrix<int> m2 = m * m;

    cout << m2 << endl << endl;

    Matrix<int> m3 = m - m;
    cout << m3 << endl;

    Matrix<int> m4 = m + m;
    cout << m4 << endl;
    return 0;
}

特殊矩阵

image.png

对角矩阵

只有对角线上可以是非零, 因此至多有 rows 个非零元素, 只需要长度为 rows 的一维数组即可表示

#include<iostream>
using namespace std;

template<class T>
class DiagonalMatrix {
public:
    DiagonalMatrix(int size = 10) {
        if (size < 0) throw "初始化对角矩阵错误!";
        n = size;
        element = new T[n];
    }
    ~DiagonalMatrix() { delete[] element; }
    T get(int, int) const;
    void set(int, int, const T&);
private:
    int n;
    T* element; // 一维数组
};

// 获取 (i, j) 值
template<class T>
T DiagonalMatrix<T>::get(int i, int j) const {
    if (i < 1 || i > n || j < 1 || j > n) {
        throw "错误索引!";
    }

    if (i == j) {
        return element[i - 1];
    }
    else {
        return 0;
    }
}

// 设置 (i, j) 值
template<class T>
void DiagonalMatrix<T>::set(int i, int j, const T& value) {
    if (i < 1 || i > n || j < 1 || j > n) {
        throw "错误索引!";
    }
    if (i == j) {
        element[i - 1] = value;
    }
    else {
        if (value != 0) throw "非对角元素必须为0!";
    }
}


int main() {
    DiagonalMatrix<int> dm;
    for (int i = 1; i <= 10; i++) {
        dm.set(i, i, i);
    }

    for (int i = 1; i <= 10; i++) {
        for (int j = 1; j <= 10; j++) {
            cout << dm.get(i, j) << " ";
        }
        cout << "\n";
    }
    return 0;
}

三对角矩阵

只有 |i-j|<=1 是非零的, 因此只需要长度为 3×rows-2 的一维数组

映射关系

(1) 采用 对角线映射 时: image.png

  1. 下对角 (i, j) 对应 a[i-2]
  2. 对角对应 a[n-1+i-1]=a[n+i-2]
  3. 上对角对应于 a[2n-1+i-1]=a[2n+i-2]

image.png

(2) 采用 逐行映射 时:

t[0:9]=[2, 1, 3, 1, 3, 5, 2, 7, 9, 0]

(3) 采用 逐列映射 时:

t=[2, 3, 1, 1, 5, 3, 2, 9, 7, 0]

📢 完整代码
#include<iostream>
using namespace std;

template<class T>
class TridiagonalMatrix {
public:
    TridiagonalMatrix(int size = 10) {
        n = size;
        element = new T[3 * n - 2];
    }
    ~TridiagonalMatrix() { delete[] element; }
    T get(int, int);
    void set(int, int, const T&);
private:
    int n;
    T* element;
};

template<class T>
T TridiagonalMatrix<T>::get(int i, int j) {
    if (i < 1 || j < 1 || i > n || j > n) throw "下标越界";

    switch (i - j) {
        case 1:
            return element[i - 2];
        case 0:
            return element[n + i - 2];
        case -1:
            return element[2 * n + i - 2];
        default: return 0;
    }
}

template<class T>
void TridiagonalMatrix<T>::set(int i, int j, const T&value) {
    if (i < 1 || i > n || j < 1 || j > n) {
        throw "错误索引!";
    }
    switch (i - j) {
        case 1:
            element[i - 2] = value;
            break;
        case 0:
            element[n + i - 2] = value;
            break;
        case -1:
            element[2 * n + i - 2] = value;
            break;
        default: if (value != 0) throw "非三对角元素必须为0!";
    }
}



int main() {
    TridiagonalMatrix<int> tm;
    for (int i = 1; i <= 10; i++) {
        for (int j = 1; j <= 10; j++) {
            if (i - j < -1 || i - j > 1) {
                tm.set(i, j, 0);
            }
            else {
                tm.set(i, j, j);
            }
        }
    }

    for (int i = 1; i <= 10; i++) {
        for (int j = 1; j <= 10; j++) {
            cout << tm.get(i, j) << " ";
        }
        cout << "\n";
    }
    return 0;
}

三角矩阵

需要长度为 n(n+1)2\frac{n(n+1)}{2} 的一维数组

采用逐行映射时:

对应 a[i×(i-1)/2+j-1] (下三角)

☃️ 完整代码
#include<iostream>
using namespace std;

template<class T>
class lowerTriangularMatrix {
public:
    lowerTriangularMatrix(int size = 10) {
        n = size;
        element = new T[n * (n + 1) / 2];
    }
    ~lowerTriangularMatrix() { delete[] element; }
    T get(int, int);
    void set(int, int, const T&);
private:
    int n;
    T* element;
};

template<class T>
T lowerTriangularMatrix<T>::get(int i, int j) {
    if (i< 1 || j < 1 || i > n || j > n) throw "错误索引!";

    if (i >= j) {
        return element[i * (i - 1) / 2 + j - 1];
    }
    else {
        return 0;
    }
}

// 采用逐行映射
template<class T>
void lowerTriangularMatrix<T>::set(int i, int j, const T& value) {
    if (i< 1 || j < 1 || i > n || j > n) throw "错误索引!";

    if (i >= j) {
        element[i * (i - 1) / 2 + j - 1] = value;
    }
    else {
        if (value != 0) {
            throw "非下三角元素必须为 0!";
        }
    }
}


int main() {
    lowerTriangularMatrix<int> ltm;
    for (int i = 1; i <= 10; i++) {
        for (int j = 1; j <= 10; j++) {
            if (i < j) {
                ltm.set(i, j, 0);
            }
            else {
                ltm.set(i, j, j);
            }
        }
    }

    for (int i = 1; i <= 10; i++) {
        for (int j = 1; j <= 10; j++) {
            cout << ltm.get(i, j) << " ";
        }
        cout << "\n";
    }
    return 0;
}

对称矩阵

与下(上)三角一致, 只需要 n(n+1)2\frac{n(n+1)}{2} 个存储空间

🌈 完整代码
template<class T>
class symmetricalMatrix {
public:
    symmetricalMatrix(int size = 10) {
        n = size;
        element = new T[n * (n + 1) / 2];
    }
    ~symmetricalMatrix() { delete[] element; }
    T get(int, int);
    void set(int, int, const T&);
private:
    int n;
    T* element;
};

template<class T>
T symmetricalMatrix<T>::get(int i, int j) {
    if (i< 1 || j < 1 || i > n || j > n) throw "错误索引!";

    if (i >= j) {
        return element[i * (i - 1) / 2 + j - 1];
    }
    else {
        return element[j * (j - 1) / 2 + i - 1];
    }
}

// 采用逐行映射
template<class T>
void symmetricalMatrix<T>::set(int i, int j, const T& value) {
    if (i< 1 || j < 1 || i > n || j > n) throw "错误索引!";

    if (i >= j) {
        element[i * (i - 1) / 2 + j - 1] = value;
    }
    else {
        element[j * (j - 1) / 2 + i - 1] = value;
    }
}

稀疏矩阵

矩阵有许多 0 元素, 且位置不确定

比如记录 1000 个顾客购买 10000 个商品的情况, 需要一个 1000 × 10000 的 purchases 矩阵来记录, 假设平均每个顾客只购买 20 个商品, 即只有 20000 个位置非空, 非空率为 0.2%, 浪费存储空间! 为了计算每个顾客的花费 spent 矩阵, 需要运算 purchasesT×pricespurchases^T×prices 运算效率低

压缩存储

即只记录非零元素的值, 利用一个 三元组 保存

template <class T>
class Term {
public:
    int row, col;
    T value;
};

image.png

不能提高 get set 的效率, 二维数组表示仅需要 O(1), 如果折半查找, get 需要 O(log(非0元素个数)), set 要移动元素, 因此需要 O(非0元素个数)

稀疏矩阵类

template<class T>
class SparseMatrix {
public:
    SparseMatrix(int maxTerms = 10);
    ~SparseMatrix() { delete[] a; };
    void transpose(SparseMatrix<T>& b) const; // 转置
    void add(const SparseMatrix<T>& b, SparseMatrix<T>& c) const; // 矩阵加法
    void reSize(int); // 重置矩阵大小
private:
    void append(const Term<T>& t);
    int rows, cols; // 矩阵行和列
    int terms; // 非零元素个数
    Term<T>* a; // 存放非零元素三元组的数组
    int MaxTerms; // 非零元素数组最大长度
};

转置

image.png

步骤
  1. 首先我们先统计每一列有多少非零元素, 因为转置后就变成行, 也就是转置矩阵每一行有多少个非零元素

image.png

  1. 对每一列非零元素做累加, 就得到转置矩阵每一行的起始位置

image.png

  1. 可以直接根据原矩阵列值 通过 rownext 获取转置矩阵的索引进行复制

image.png

template<class T>
void SparseMatrix<T>::transpose(SparseMatrix<T>& b) const {
    if (terms > b.MaxTerms) {
        b.reSize(terms);
    };

    b.cols = rows;
    b.rows = cols;
    b.terms = terms;

    // 统计每一列非零元素个数
    // 这里为方便列与索引一一对应, 创建 cols + 1 大小的数组
    int* ColSize = new int[cols + 1];
    for (int i = 1; i <= cols; i++) {
        ColSize[i] = 0;
    }
    for (int i = 0; i < terms; i++) {
        ColSize[a[i].col]++;
    }

    // 做累加
    int* RowNext = new int[cols + 1];
    RowNext[1] = 0;
    for (int i = 2; i <= cols; i++) {
        RowNext[i] = RowNext[i - 1] + ColSize[i - 1];
    }

    // 复制元素
    for (int i = 0; i < terms; i++) {
        int j = RowNext[a[i].col]++; // 放了一个元素后后移一位
        b.a[j].row = a[i].col;
        b.a[j].col = a[i].row;
        b.a[j].value = a[i].value;
    }
}

时间复杂度:

O(cols+terms)O(cols+terms)

矩阵相加

类似于两个有序链表的相加, 一个存放结果的数组 c, 再遍历两个数组, 谁元素小的加进 c, 然后指针后移, 直到其中一个遍历结束

// 矩阵相加
// b: 要加的矩阵, c: 保存结果的矩阵
template<class T>
void SparseMatrix<T>::add(const SparseMatrix<T>& b, SparseMatrix<T>& c) const {
    if (rows != b.rows || cols != b.cols) {
        throw "非同型矩阵不能相加";
    }

    // c 矩阵初始化
    c.rows = rows;
    c.cols = cols;
    c.terms = 0;

    // 双"指针", 分别遍历当前矩阵和 b 矩阵
    int ct = 0, cb = 0;

    // 因为行是有序的, 所以一个矩阵遍历结束后就可以退出循环
    while (ct < terms && cb < b.terms) {
        // 计算两个元素的行主次序编号
        int indt = a[ct].row * cols + a[ct].col;
        int indb = b.a[cb].row * cols + b.a[cb].col;
        
        // b 的元素在后
        if (indt < indb) {
            // 那么就往 c 中添加 a 的元素
            c.append(a[ct]);
            ct++;
        }
        else if (indt == indb) { // 两个元素次序相同, 则相加
            if (a[ct].value + b.a[cb].value) {
                // 仅非零元素需要加进去
                Term<T> t;
                t.row = a[ct].row;
                t.col = a[ct].col;
                t.value = a[ct].value + b.a[cb].value;
                c.append(t);
            }
            ct++; cb++;
        }
        else {
            // b 的次序在前, 把 b 加进 c
            c.append(b.a[cb]);
            cb++;
        }
    }

    // 将剩下的元素都是按顺序的, 直接加进去
    for (; ct < terms; ct++) {
        c.append(a[ct]);
    }

    for (; cb < b.terms; cb++) {
        c.append(b.a[cb]);
    }
}

时间复杂度(最多):

O(x.terms+b.terms)O(x.terms + b.terms)

矩阵相乘

image.png

由于结果的非零项不确定, 因此只能先使用一个二维数组来保存计算结果

假设矩阵 a 和矩阵 b 相乘得矩阵 c, 则:

c1,1=a1,1×b1,1+a1,2×b2,1+c_{1,1}=a_{1,1}×b_{1,1}+a_{1,2}×b_{2,1}+\cdots

也就是说, a 矩阵中 i 的只能和 b 矩阵中 i 的相乘

image.png

值得注意的是, (1,1) 这个位置不止两个数相加(根据数学公式可以明显得出), 比如下面的 a(1,7)b(7,1)

image.png

对于稀疏矩阵三元组的相加办法呼之欲出了

遍历两个稀疏矩阵, 找到左矩阵 与右矩阵 相等的, 其值相乘, += 到左矩阵 , 右矩阵 对应的二维数组中, 最后遍历二维数组, 抽出值不为 0 的加到 c 矩阵中即可

核心代码:

// 开始遍历稀疏矩阵
for (int i = 0; i < terms; i++) {
    for (int j = 0; j < b.terms; j++) {
        // 左矩阵元素的列要和右矩阵元素的行相对应才能相乘
        if (a[i].col == b.a[j].row) {
            res[a[i].row][b.a[j].col] += a[i].value * b.a[j].value;
        }
    }
}

完整代码:

template<class T>
void SparseMatrix<T>::multiply(const SparseMatrix<T>& b, SparseMatrix<T>& c) const {
    if (cols != b.rows) throw "矩阵不兼容, 无法相乘";

    // 初始化结果矩阵(rows × b.cols 矩阵)
    // 0 位置不要了
    T** res = new T* [rows + 1];
    for (int i = 1; i <= rows; i++) {
        res[i] = new T[b.cols + 1];
    }
    // 结果全部初始化为 0
    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= b.cols; j++) {
            res[i][j] = 0;
        }
    }

    c.rows = rows;
    c.cols = b.cols;

    // 开始遍历稀疏矩阵
    for (int i = 0; i < terms; i++) {
        for (int j = 0; j < b.terms; j++) {
            // 左矩阵元素的列要和右矩阵元素的行相对应才能相乘
            if (a[i].col == b.a[j].row) {
                res[a[i].row][b.a[j].col] += a[i].value * b.a[j].value;
            }
        }
    }

    // 遍历结果数组, 值不为 0 的存进 c 中
    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= b.cols; j++) {
            if (res[i][j] != 0){
                Term<T> t(i, j, res[i][j]);
                c.append(t);
            }
        }
    }

    for (int i = 1; i <= rows; i++) {
        delete[] res[i];
    }
    delete[] res;
}
🐣 完整代码
#include<iostream>
using namespace std;

// 三元组
template <class T>
class Term {
public:
    Term() {
        row = col = value = 0;
    };
    Term(int r, int c, T v) : row(r), col(c), value(v) {};
    int row, col;
    T value;
};

template<class T>
class SparseMatrix {
    friend ostream& operator << (ostream& out, const SparseMatrix<T>& x) {
        out << "行 = " << x.rows << " 列 = " << x.cols << endl;
        out << "非零元素个数 = " << x.terms << endl;

        for (int i = 0; i < x.terms; i++) {
            out << "(" << x.a[i].row << ',' << x.a[i].col
                << ") = " << x.a[i].value << endl;
        }
        return out;
    };
    friend istream& operator >> (istream& in, SparseMatrix<T>& x) {
        cout << "输入矩阵的行, 列以及总共要输入多少数据:";
        in >> x.rows >> x.cols >> x.terms;
        if (x.terms > x.MaxTerms) { // 确保数组不会越界
            x.reSize(x.terms);
        };
        for (int i = 0; i < x.terms; i++) {
            cout << "输入第" << (i + 1) << "个元素的行, 列以及值: ";
            in >> x.a[i].row >> x.a[i].col >> x.a[i].value;
        }
        return in;
    };
public:
    SparseMatrix(int maxTerms = 10);
    ~SparseMatrix() { delete[] a; };
    void transpose(SparseMatrix<T>& b) const;
    void add(const SparseMatrix<T>& b, SparseMatrix<T>& c) const;
    void multiply(const SparseMatrix<T>& b, SparseMatrix<T>& c) const;
    int Columns() { return cols; };
    int Rows() { return rows; };
    void reSize(int);
private:
    void append(const Term<T>& t);
    int rows, cols; // 矩阵行和列
    int terms; // 非零元素个数
    Term<T>* a; // 存放非零元素三元组的数组
    int MaxTerms; // 非零元素数组最大长度
};

// 构造函数
template<class T>
SparseMatrix<T>::SparseMatrix(int maxTerms) {
    if (maxTerms < 1) throw "初始化错误";
    MaxTerms = maxTerms;
    a = new Term<T>[MaxTerms];
    terms = rows = cols = 0;
}

// 矩阵转置
template<class T>
void SparseMatrix<T>::transpose(SparseMatrix<T>& b) const {
    if (terms > b.MaxTerms) {
        b.reSize(terms);
    };

    b.cols = rows;
    b.rows = cols;
    b.terms = terms;

    // 统计每一列非零元素个数
    // 这里为方便列与索引一一对应, 创建 cols + 1 大小的数组
    int* ColSize = new int[cols + 1];
    for (int i = 1; i <= cols; i++) {
        ColSize[i] = 0;
    }
    for (int i = 0; i < terms; i++) {
        ColSize[a[i].col]++;
    }

    // 做累加
    int* RowNext = new int[cols + 1];
    RowNext[1] = 0;
    for (int i = 2; i <= cols; i++) {
        RowNext[i] = RowNext[i - 1] + ColSize[i - 1];
    }


    for (int i = 0; i < terms; i++) {
        int j = RowNext[a[i].col]++; // 放了一个元素后后移一位
        b.a[j].row = a[i].col;
        b.a[j].col = a[i].row;
        b.a[j].value = a[i].value;
    }
}

// 矩阵相加
// b: 要加的矩阵, c: 保存结果的矩阵
template<class T>
void SparseMatrix<T>::add(const SparseMatrix<T>& b, SparseMatrix<T>& c) const {
    if (rows != b.rows || cols != b.cols) {
        throw "非同型矩阵不能相加";
    }

    // c 矩阵初始化
    c.rows = rows;
    c.cols = cols;
    c.terms = 0;

    // 双"指针", 分别遍历当前矩阵和 b 矩阵
    int ct = 0, cb = 0;

    // 因为行是有序的, 所以一个矩阵遍历结束后就可以退出循环
    while (ct < terms && cb < b.terms) {
        // 计算两个元素的行主次序编号
        int indt = a[ct].row * cols + a[ct].col;
        int indb = b.a[cb].row * cols + b.a[cb].col;

        // b 的元素在后
        if (indt < indb) {
            // 那么就往 c 中添加 a 的元素
            c.append(a[ct]);
            ct++;
        }
        else if (indt == indb) { // 两个元素次序相同, 则相加
            if (a[ct].value + b.a[cb].value) {
                // 仅非零元素需要加进去
                Term<T> t(a[ct].row, a[ct].col, a[ct].value + b.a[cb].value);
                c.append(t);
            }
            ct++; cb++;
        }
        else {
            c.append(b.a[cb]);
            cb++;
        }
    }

    // 将剩下的元素直接加进去
    for (; ct < terms; ct++) {
        c.append(a[ct]);
    }

    for (; cb < b.terms; cb++) {
        c.append(b.a[cb]);
    }
}

// 矩阵相乘
template<class T>
void SparseMatrix<T>::multiply(const SparseMatrix<T>& b, SparseMatrix<T>& c) const {
    if (cols != b.rows) throw "矩阵不兼容, 无法相乘";

    // 初始化结果矩阵(rows × b.cols 矩阵)
    // 0 位置不要了
    T** res = new T* [rows + 1];
    for (int i = 1; i <= rows; i++) {
        res[i] = new T[b.cols + 1];
    }
    // 结果全部初始化为 0
    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= b.cols; j++) {
            res[i][j] = 0;
        }
    }

    c.rows = rows;
    c.cols = b.cols;

    // 开始遍历稀疏矩阵
    for (int i = 0; i < terms; i++) {
        for (int j = 0; j < b.terms; j++) {
            // 左矩阵元素的列要和右矩阵元素的行相对应才能相乘
            if (a[i].col == b.a[j].row) {
                res[a[i].row][b.a[j].col] += a[i].value * b.a[j].value;
            }
        }
    }

    // 遍历结果数组, 值不为 0 的存进 c 中
    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= b.cols; j++) {
            if (res[i][j] != 0){
                Term<T> t(i, j, res[i][j]);
                c.append(t);
            }
        }
    }

    for (int i = 1; i <= rows; i++) {
        delete[] res[i];
    }
    delete[] res;
}

// 矩阵重新设置大小
template<class T>
void SparseMatrix<T>::reSize(int sz) {
    Term<T>* temp = new Term<T>[sz];
    copy(a, a + terms, temp);
    delete[] a;
    a = temp;
    MaxTerms = sz;
}

// 尾部添加新元素
template<class T>
void SparseMatrix<T>::append(const Term<T>& t) {
    if (terms >= MaxTerms) {
        this->reSize(MaxTerms * 2);
    };
    a[terms] = t;
    terms++;
}


int main() {
    SparseMatrix<int> a;
    cin >> a;
    cout << a << endl;

    SparseMatrix<int> b;
    cin >> b;
    cout << b << endl;

    SparseMatrix<int> c;
    a.multiply(b, c);

    cout << c;

    return 0;
}

链表表示

image.png