一个 O(n^3) 计算矩阵特征多项式的算法

先看一个粗暴的朴素算法

首先是矩阵特征多项式的定义:

φA(λ)=det(AλI)\varphi_A(\lambda) = \det(A - \lambda I)

最直接的做法就是直接按行展开矩阵行列式并递归计算。这样是一个 O(n!)O(n!) 的暴力算法,显然是不能接受的。

再看一个多项式复杂度的简单算法

由朴素算法可知,φA(λ)\varphi_A(\lambda) 是一个 nn 阶多项式,于是可以带入 n+1n+1 个点进行插值(例如 λ=0...n\lambda = 0...n )。然后 O(n3)O(n^3) 地求出一个矩阵的行列式,总体的复杂度就是 O(n4)O(n^4) 了。

最后是一个优秀的 n^3 算法

先是化简矩阵的部分

由于我本人是一个脏脏的 CS 人,所以我并不打算从 householder-transform 的角度下手 其实是看不懂

由相似的性质知,若 B=PAP1B = PAP^{-1}A,BA,B 的特征多项式相同。如果能使 BB 满足某些性质能更轻松地求出特征多项式,就能借助求解 BB 的特征多项式得到 AA 的特征多项式。

于是考虑通过初等变换来构造 PP

  1. 行变化:Ei,jAEi,j1E_{i,j} A E_{i,j}^{-1} ,互换第 i,ji,j 行、第 i,ji,j 列。
  2. 行倍乘:Ei(k)AEi1(k)E_i(k) A E_{i}^{-1}(k) ,第 ii 行乘以 kk,第 ii 列乘以 1k\frac{1}{k}
  3. 行倍加:Ei,j(k)AEi,j1(k)E_{i,j}(k) A E_{i,j}^{-1}(k),第 ii 行加上 kk 倍的第 jj 行,第 jj 列减去 kk 倍的第 ii 列。

由于每次对第 ii 行的操作都会影响到第 ii 列,所以考虑将 AA 化简为 Upper-Hessenberg 矩阵 上海堡垒矩阵,而非常见的上三角矩阵。

a1,1a1,2a1,3a1,n1a1,na2,1a2,2a2,3a2,n1a2,n0a3,2a3,3a3,n1a3,n00a4,3a4,n1a4,n000an1,nan,n\begin{matrix} a_{1,1} & a_{1,2} & a_{1,3} & \dots & a_{1,n-1} & a_{1,n} \\ a_{2,1} & a_{2,2} & a_{2,3} & \dots & a_{2,n-1} & a_{2,n} \\ 0 & a_{3,2} & a_{3,3} & \dots & a_{3,n-1} & a_{3,n} \\ 0 & 0 & a_{4,3} & \dots & a_{4,n-1} & a_{4,n} \\ \vdots & \vdots & \vdots & & \vdots & \vdots \\ 0 & 0 & 0 & \dots & a_{n-1,n} & a_{n,n} \end{matrix}

这样处理第 ii 列时,对应的主元在第 i+1i+1 行,就不会因为行倍加操作影响到第 ii 列了。

那么如何计算上海森堡矩阵的特征多项式呢

由于 BBAA 特征多项式一致,所以我们只要算出 BB 的特征多项式即可。

令第 ii 行到第 nn 行,第 ii 列到第 nn 列相交形成的矩阵的特征多项式为 Di(x)D_i(x) 。特别的,令 Dn+1(x)=1D_{n+1}(x) = 1。则按照第一列展开可以得到:

Di(x)=(ai,ix)Di+1(x)+j=i+1nai,j(1)jiDj+1(x)k=i+1jak,k1D_i(x) = (a_{i,i} - x) D_{i+1}(x) + \sum_{j = i + 1}^na_{i, j}(-1)^{j - i}D_{j+1}(x) \prod_{k=i+1}^{j} a_{k,k-1}

这样直接后往前计算 Di(x)D_i(x) ,复杂度也是 O(n3)O(n^3) 的。加上前面化简矩阵,整体的复杂度就是 O(n3)O(n^3) 了。

一个参考代码:

#include <iostream>
#include <vector>
#include <algorithm>

typedef std::vector<std::vector<double>> matrix;
typedef std::vector<double> polynomial;

void swapLine(matrix &A, int i, int j) {
    std::swap(A[i], A[j]);
}

void swapColumn(matrix &A, int i, int j) {
    for (auto& k : A) {
        std::swap(k[i], k[j]);
    }
}

void addLine(matrix &A, int i, int j, double k) {
    for (int l = 0; l < A[0].size(); l++) {
        A[i][l] += k * A[j][l];
    }
}

void addColumn(matrix &A, int i, int j, double k) {
    for (auto& l : A) {
        l[i] += k * l[j];
    }
}

matrix simplify(matrix A) {
    int n = static_cast<int>(A.size());

    for (int i = 0; i + 1 < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (A[j][i] != 0) {
                if (j != i + 1) {
                    swapLine(A, i + 1, j);
                    swapColumn(A, i + 1, j);
                }
                break;
            }
        }

        if (A[i + 1][i] == 0) {
            continue;
        }

        for (int k = i + 2; k < n; k++) {
            double t = A[k][i] / A[i + 1][i];
            addLine(A, k, i + 1, -t);
            addColumn(A, i + 1, k, t);
        }
    }

    return A;
}

polynomial calcPolynomial(const matrix& A) {
    int n = static_cast<int>(A.size());
    auto D = std::vector(n + 1, polynomial());

    D[n] = {1};

    for (int i = n - 1; i >= 0; i--) {
        D[i].resize(n - i + 1);

        double k = 1;
        for (int j = i + 1; j < n; j++) {
            k *= A[j][j - 1];

            for (int l = 0; l < D[j + 1].size(); l++) {
                D[i][l] += ((j - i) % 2 == 0 ? 1 : -1) * A[i][j] * k * D[j + 1][l];
            }
        }

        for (int j = 0; j < D[i + 1].size(); j++) {
            D[i][j] += A[i][i] * D[i + 1][j];
            D[i][j + 1] -= D[i + 1][j];
        }
    }

    return D[0];
}

int main() {
    matrix m = {
        {1, 1, 1},
        {1, 2, 3},
        {1, 3, 6}
    };

    m = simplify(m);
    auto p = calcPolynomial(m);

    for (int i = 0; i < p.size(); i++) {
        if (i == 0) {
            std::cout << p[i];
        } else {
            std::cout << " + " << p[i] << "x^" << i;
        }
    }

    std::cout << std::endl;

    return 0;
}

一个 O(n^3) 计算矩阵特征多项式的算法
https://littlejianch.github.io/compute-characteristic-polynomial-in-On3-time/
Author
LittleJian
Posted on
June 3, 2022
Licensed under