特征多项式
特征的这部分只研究方阵,即矩阵
由于在实际问题中,经常要考虑连续进行重复的变换,如果只用“矩阵
然而事实上,矩阵
特征值与特征向量
在矩阵
设
则称
特征向量在同一直线上,在线性变换作用下保持方向不改变(压缩到零也认为是方向不改变)。特征向量不唯一,与特征向量共线的向量都是特征向量,但是规定零向量不是特征向量,拥有方向的向量自然是非零向量。特征向量的特征值就是它伸缩的倍数。
在实际应用中,一般对于拥有相同特征值的特征向量,会选取一组基作为它们全体的代表。
设
设
于是有:
所以相应的行列式也为
特征多项式
考虑一个
特征矩阵的行列式称为
其中
相应于
线性变换
线性变换
根据代数基本定理,特征多项式可以分解为:
称
求解矩阵的全部特征值及特征向量
分为以下步骤:
- 计算行列式
。 - 求出多项式
在域 中的全部根,即 的特征值。 - 对
的每个特征值 ,解齐次线性方程组 ,求出它的一组基础解系 ,则 的属于 的全部特征向量为:
该表达式中的
- 线性变换
的属于 的特征向量为:
因此,属于
该表达式中的
特征值与特征向量是否存在,依赖于
相似变换
引入
若
那么
可轻松求得,下三角矩阵也是类似的。但如果
定义
对于
则矩阵
考虑
得证,对于
定理:相似矩阵有相同的特征多项式及特征值,反之不然。
定理表明,线性变换的矩阵的特征多项式与基的选取无关,而直接由线性变换决定,故可称之为线性变换的特征多项式。
矩阵
其中
根据韦达定理,特征多项式的常数项为:
定理:相似的矩阵有相同的迹。
换位公式
定理:无论矩阵
一种证法是直接展开,即证毕。另一种证法用到换位公式。
定理:设
该公式表明
舒尔(Schur)引理
任意的
推论:设
特别地,
使用高斯消元进行相似变换
对
在对矩阵使用上述操作(左乘初等矩阵)后再右乘其逆矩阵即相似变换,左乘为行变换,易发现右乘即列变换。
若能将矩阵通过相似变换变为上三角或下三角的形式,那么可以轻松求出其特征多项式。但若对主对角线上的元素应用变换
后文将说明对次对角线上的元素应用变换后得到的矩阵依然可以轻松得到其特征多项式。
上 Hessenberg 矩阵
对于
的矩阵我们称为上 Hessenberg 矩阵,其中
我们使用相似变换将次对角线以下的元素消为零后即能得到上 Hessenberg 矩阵,而求出一个
我们记
在计算行列式时我们一般选择按零最多的行或列余子式展开,余子式即删除了当前选择的元素所在行和列之后的矩阵,在这里我们选择按最后一行进行展开,有
观察并归纳,对
至此完成了整个算法,该算法一般被称为 Hessenberg 算法。
哈密顿凯莱(Hamilton-Cayley)定理
对于任意的
对于线性变换
由本定理可知,对于任意的矩阵
最小多项式
设
对于一个特定的线性变换
可以将矩阵
根据多项式的辗转相除法,最小多项式是唯一的,且可整除任一
定理:在不计重数的情况下,矩阵
定理:矩阵
应用
在信息学中我们一般考虑
#include <cassert>
#include <iostream>
#include <random>
#include <vector>
typedef std::vector<std::vector<int>> Matrix;
typedef long long i64;
Matrix to_upper_Hessenberg(const Matrix &M, int mod) {
Matrix H(M);
int n = H.size();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if ((H[i][j] %= mod) < 0) H[i][j] += mod;
}
}
for (int i = 0; i < n - 1; ++i) {
int pivot = i + 1;
for (; pivot < n; ++pivot) {
if (H[pivot][i] != 0) break;
}
if (pivot == n) continue;
if (pivot != i + 1) {
for (int j = i; j < n; ++j) std::swap(H[i + 1][j], H[pivot][j]);
for (int j = 0; j < n; ++j) std::swap(H[j][i + 1], H[j][pivot]);
}
for (int j = i + 2; j < n; ++j) {
for (;;) {
if (H[j][i] == 0) break;
if (H[i + 1][i] == 0) {
for (int k = i; k < n; ++k) std::swap(H[i + 1][k], H[j][k]);
for (int k = 0; k < n; ++k) std::swap(H[k][i + 1], H[k][j]);
break;
}
if (H[j][i] >= H[i + 1][i]) {
int q = H[j][i] / H[i + 1][i], mq = mod - q;
for (int k = i; k < n; ++k)
H[j][k] = (H[j][k] + i64(mq) * H[i + 1][k]) % mod;
for (int k = 0; k < n; ++k)
H[k][i + 1] = (H[k][i + 1] + i64(q) * H[k][j]) % mod;
} else {
int q = H[i + 1][i] / H[j][i], mq = mod - q;
for (int k = i; k < n; ++k)
H[i + 1][k] = (H[i + 1][k] + i64(mq) * H[j][k]) % mod;
for (int k = 0; k < n; ++k)
H[k][j] = (H[k][j] + i64(q) * H[k][i + 1]) % mod;
}
}
}
}
return H;
}
std::vector<int> get_charpoly(const Matrix &M, int mod) {
Matrix H(to_upper_Hessenberg(M, mod));
int n = H.size();
std::vector<std::vector<int>> p(n + 1);
p[0] = {1 % mod};
for (int i = 1; i <= n; ++i) {
const std::vector<int> &pi_1 = p[i - 1];
std::vector<int> &pi = p[i];
pi.resize(i + 1, 0);
int v = mod - H[i - 1][i - 1];
if (v == mod) v -= mod;
for (int j = 0; j < i; ++j) {
pi[j] = (pi[j] + i64(v) * pi_1[j]) % mod;
if ((pi[j + 1] += pi_1[j]) >= mod) pi[j + 1] -= mod;
}
int t = 1;
for (int j = 1; j < i; ++j) {
t = i64(t) * H[i - j][i - j - 1] % mod;
int prod = i64(t) * H[i - j - 1][i - 1] % mod;
if (prod == 0) continue;
prod = mod - prod;
for (int k = 0; k <= i - j - 1; ++k)
pi[k] = (pi[k] + i64(prod) * p[i - j - 1][k]) % mod;
}
}
return p[n];
}
bool verify(const Matrix &M, const std::vector<int> &charpoly, int mod) {
if (mod == 1) return true;
int n = M.size();
std::vector<int> randvec(n), sum(n, 0);
std::mt19937 gen(std::random_device{}());
std::uniform_int_distribution<int> dis(1, mod - 1);
for (int i = 0; i < n; ++i) randvec[i] = dis(gen);
for (int i = 0; i <= n; ++i) {
int v = charpoly[i];
for (int j = 0; j < n; ++j) sum[j] = (sum[j] + i64(v) * randvec[j]) % mod;
std::vector<int> prod(n, 0);
for (int j = 0; j < n; ++j) {
for (int k = 0; k < n; ++k) {
prod[j] = (prod[j] + i64(M[j][k]) * randvec[k]) % mod;
}
}
randvec.swap(prod);
}
for (int i = 0; i < n; ++i)
if (sum[i] != 0) return false;
return true;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int n, mod;
std::cin >> n >> mod;
Matrix M(n, std::vector<int>(n));
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j) std::cin >> M[i][j];
std::vector<int> charpoly(get_charpoly(M, mod));
for (int i = 0; i <= n; ++i) std::cout << charpoly[i] << ' ';
assert(verify(M, charpoly, mod));
return 0;
}
上述 Hessenberg 算法不具有数值的稳定性,所以
我们可以将特征多项式与常系数齐次线性递推联系起来,也可结合 Cayley-Hamilton 定理、多项式取模加速一些域上求矩阵幂次的算法。
Cayley-Hamilton 定理指出
其中
若我们要求
而
令
参考文献
- Rizwana Rehman, Ilse C.F. Ipsen.La Budde’s Method for Computing Characteristic Polynomials.
- Marshall Law.Computing Characteristic Polynomials of Matrices of Structured Polynomials.
- Mike Paterson.On the Number of Nonscalar Multiplications Necessary to Evaluate Polynomials.
贡献者:@Great-designer@Yuchen@WenzelTian
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用