Java教程

矩阵入门

本文主要是介绍矩阵入门,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

矩阵

向量与矩阵

在线性代数中,向量分为列向量和行向量。

向量也是特殊的矩阵,行向量可以看作是一个 \(1\times n\) 的矩阵,例如下面这样:

\[\begin{bmatrix} 1&2&3&4&5 \end{bmatrix} \]

列向量可以看作是一个 \(n\times 1\) 的矩阵,例如下面这样:

\[\begin{bmatrix} 1\\2\\3\\4\\5 \end{bmatrix} \]

也可以写成由行向量转置而来的,比如上面的列向量可以这样写:

\[\begin{bmatrix} 1&2&3&4&5 \end{bmatrix}^{T} \]

线性代数的主要研究对象是列向量,约定使用粗体小写字母表示列向量。在用到大量向量与矩阵的线性在数中,不引起混淆的情况下,在手写时,字母上方的向量记号可以省略不写。

如果想要表示行向量,需要在粗体小写字母右上方写转置符号。行向量在线性代数中一般表示方程。

引入

矩阵的引入来自于线性方程组,与向量类似,都是为了能以更少的字符来表示更多的信息,就比如下面的线性方程组:

\[\left\{\begin{matrix} 7x_{1}+8x_{2}+9x_{3}=13 \\ 4x_{1}+5x_{2}+6x_{3}=12 \\ 1x_{1}+2x_{2}+3x_{3}=11 \end{matrix}\right. \]

一般使用圆括号或方括号来表示矩阵。上面的方程组写成矩阵的形式就是:

\[\begin{bmatrix} 7& 8& 9\\ 4& 5& 6\\ 1& 2& 3 \end{bmatrix} \begin{bmatrix} x_{1}\\ x_{2}\\ x_{3} \end{bmatrix} = \begin{bmatrix} 13\\ 12\\ 11 \end{bmatrix} \]

我觉得方括号更整齐好看于是就用的方括号

简记为:

\[Ax=b \]

表示位置数列向量 \(x\) 左乘一个矩阵 \(A\),得到列向量 \(b\)。这个式子可以认为是线性代数的基本形式。

线性代数主要研究的运算模型是内积,内积是先相乘再相加,是行向量左乘列向量,得到一个数的过程。矩阵乘法是内积的拓展,矩阵乘法等价于左边矩阵拿出来一行与右边矩阵抽出一列来进行内积,得到结果矩阵的对应元素,口诀「左行右列」。

定义

对于矩阵 \(A\),主对角线是指 \(A_{i,i}\) 的元素。

一般用 \(I\) 来表示单位矩阵,就是主对角线上为 \(1\),其余位置为 \(0\) 的矩阵。

同型矩阵

两个矩阵,行数和列数对应相同,则称为同型矩阵。

方阵

行数等于列数的矩阵称为方阵。方阵是一种特殊的矩阵。对于「\(n\) 阶矩阵」的习惯表述,实际上讲的就是 \(n\) 阶方阵。阶数相同的方阵为同型矩阵。

主对角线

方阵中行数等于列数的元素构成主对角线

对称矩阵

如果将方阵的元素关于主对角线对称,即对于任意的 \(i\)\(j\),都有 \(a_{ij}=a_{ji}\),则称该方阵为对称矩阵,其中 \(a_{ij}\) 表示矩阵 \(A\) 中第 \(i\) 行第 \(j\) 列的元素

对角矩阵

主对角线之外的元素均为 \(0\) 的方阵称为对角矩阵,一般记作:

\[\text{diag}\left\{\lambda_{1},\ldots,\lambda_{}n\right\} \]

式子中的 \(\lambda_{1},\ldots,\lambda_{n}\) 是主对角线上的元素。

对角矩阵是对称矩阵。

如果对角矩阵的元素均为 \(1\),则称为单位矩阵,记为 \(I\)。只要乘法可以进行,任何矩阵乘单位矩阵仍保持不变。

三角矩阵

如果方阵主对角线左下方的元素均为 \(0\),则称为上三角矩阵,如果方阵主对角线右上方元素均为 \(0\),则称为下三角矩阵。

两个上(下)三角矩阵的乘积仍是上(下)三角矩阵。如果对角线元素均非 \(0\),则上(下)三角矩阵可逆,逆也是上(下)三角矩阵。

单位三角矩阵

如果上三角矩阵 \(A\) 的对角线全为 \(1\),则称 \(A\) 是单位上三角矩阵。如果下三角矩阵 \(A\) 的对角线全为 \(1\),则称 \(A\) 是单位下三角矩阵。

两个单位上(下)三角矩阵的乘积仍然是单位上(下)三角矩阵,单位上(下)三角矩阵的逆也是单位上(下)三角矩阵。

运算

矩阵的线性运算分为加减法与数乘,他们均为逐个元素进行。只有同型矩阵之间可以对应相加减。

矩阵的转置

矩阵的转置,就是在矩阵的右上角写上转置「T」记号,表示将矩阵的行与列互换。

对称矩阵转置前后保持不变。

矩阵乘法

矩阵的乘法是向量内积的推广。

矩阵相乘只有在第一个矩阵的列数和第二个矩阵的行数相同时才有意义。

\(A\)\(P\times M\) 的矩阵,\(B\)\(M\times Q\) 的矩阵,设 \(C\) 矩阵为矩阵 \(A\)\(B\) 的乘积。

其中矩阵 \(C\) 中的第 \(i\) 行第 \(j\) 列的元素可以表示为:

\[C_{ij}=\sum_{k=1}^{M}A_{ik}B_{kj} \]

找了张好理解的图片:

image

原文:B2105 矩阵乘法 题解 - Daidly's Blog - 洛谷博客

在矩阵乘法中,结果 \(C\) 矩阵的第 \(i\) 行第 \(j\) 列的数,就是由矩阵 \(A\)\(i\)\(M\) 个数与矩阵 \(B\)\(j\)\(M\) 个数分别相乘再相加得到的。这里的相乘再相加,就是向量的内积。乘积矩阵中第 \(i\) 行第 \(j\) 列的数恰好是乘数矩阵 \(A\)\(i\) 个行向量与乘数矩阵 \(B\)\(j\) 个列向量的内积。

注意矩阵乘法满足结合律但不满足一般的交换律

利用结合律,矩阵乘法可以利用快速幂的思想来优化

小优化

对于较小的矩阵,可以直接手动展开循环来减小常数。

可以重新排列循环以提高空间局部性,这样的优化不会改变时间复杂度,但是可以得到常数上的提升。

矩阵快速幂

快速幂都会,那矩阵快速幂呢?

在后面的矩阵加速递推等都需要用到他,原理是什么?

大致的写法分为两种:重载运算符和函数,我个人比较喜欢函数的写法。

我们可以按正常的快速幂写法来,区别就是在其中的底数换成了矩阵,然后写一个矩阵乘法的函数来进行相乘即可。

P3390【模板】矩阵快速幂 - 洛谷

参考代码:

#include<bits/stdc++.h>
#define int long long
#define P 1000000007
#define endl '\n'
#define N 110 
using namespace std;
struct sb{int m[N][N];}ans;
int n,k;
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
inline sb cheng(sb a,sb b)
{
    sb c;
    for(int i=1;i<=n;i++)
    {
          for(int j=1;j<=n;j++)
          {
              c.m[i][j]=0;
            for(int k=1;k<=n;k++)
              c.m[i][j]=(c.m[i][j]+a.m[i][k]*b.m[k][j])%P;
          }
    }
    return c;
}
inline sb jzksm(sb x,int y)
{
    sb res=x;y--;
    while(y)
    {
        if(y&1)res=cheng(res,x);
        x=cheng(x,x);
        y>>=1;
    }
    return res;
}
signed main()
{
    n=read();k=read();
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
        ans.m[i][j]=read();
    ans=jzksm(ans,k);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
          cout<<ans.m[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}

方阵的逆

方阵 \(A\) 的逆矩阵 \(P\) 是使得 \(A\times P=I\) 的矩阵。

逆矩阵不一定存在,如果存在可以用高斯消元来求解。

方阵的行列式

行列式是方阵的一种运算。

看待线性方程组的两种视角

看待一个矩阵 \(A\),有两种视角。

第一种观点:按行看,观察 \(A\) 的每一行,这样一来把 \(A\) 看作方程组。于是就有了消元法解方程的过程。

第二种观点:按列看,观察 \(A\) 的每一列,\(A\) 本身也是由列向量构成的。此时相当于把变换 \(A\) 本身看成了列向量组,而 \(x\) 是未知数系数,思考 \(A\) 当中的这组列向量能不能配上未知数,凑出列向量 \(b\)

例如:文章开头的例子变为:

\[\begin{bmatrix} 7\\4\\1 \end{bmatrix}x_{1}+\begin{bmatrix} 8\\5\\2 \end{bmatrix}x_{2}+\begin{bmatrix} 9\\6\\3 \end{bmatrix}x_{3}= \begin{bmatrix} 13\\12\\11 \end{bmatrix} \]

解方程变为研究是否可以通过调整三个系数 \(x\) 使得给定的三个基向量能够凑出结果的向量。

按列看比按行看更新颖。在按列看的视角下,可以研究线性无关与线性相关。

矩阵乘法的应用

矩阵加速递推

斐波那契数列大家应该非常熟悉,在学习递归或者递推的时候应该是有做过相关题目的,在斐波那契数列里面,\(F_{1}=F_{2}=1,F_{i}=F_{i-1}+F_{i-2}(i\ge3)\)

我们在之前的题目遇见的求斐波那契数列第 \(n\) 项的值范围都是很小的,因为递归的速度太慢,如果数据范围到达了 \(10^{18}\) 那么我们递推也是一定 TLE 的,所以这个时候就需要用到我们的矩阵加速递推。

\(Fib(n)\) 表示一个 \(1\times 2\) 的矩阵 \(\begin{bmatrix}F_{n}&F_{n+1}\end{bmatrix}\) 。我们希望依据 \(Fib(n-1)=\begin{bmatrix}F_{n-1}&F_{n-2}\end{bmatrix}\) 推出 \(Fib(n)\)

试着来推导一个矩阵 \(\text{base}\),使 \(Fib(n-1)\times \text{base}=Fib(n)\),也就是 \(\begin{bmatrix}F_{n-1}&F_{n-2}\end{bmatrix}\times \text{base}=\begin{bmatrix}F_{n}&F_{n-1}\end{bmatrix}\)

因为 \(F_{n}=F_{n-1}+F_{n-2}\),所以 \(\text{base}\) 矩阵第一列一定是 \(\begin{bmatrix}1\\1\end{bmatrix}\),这样才能在进行乘法运算的时候才能令 \(F_{n-1}\)\(F_{n-2}\) 相加,从而得出 \(F_{n}\)。同理,为了得出 \(F_{n-1}\),矩阵 \(\text{base}\) 的第二列应该为 \(\begin{bmatrix}1\\0\end{bmatrix}\)

综上所述,\(\text{base}=\begin{bmatrix}1&1\\1&0\end{bmatrix}\) ,原式化为 \(\begin{bmatrix}F_{n-1}&F_{n-2}\end{bmatrix}\times \begin{bmatrix}1&1\\1&0\end{bmatrix}=\begin{bmatrix}F_{n}&F_{n-1}\end{bmatrix}\)

定义初始矩阵 \(ans=\begin{bmatrix}F_{2}&F_{1}\end{bmatrix}=\begin{bmatrix}1&1\end{bmatrix}\)\(\text{base}=\begin{bmatrix}1&1\\1&0\end{bmatrix}\)。那么,\(F_{n}\) 就等于 \(ans\times \text{base}^{n-2}\) 这个矩阵的第一行第一列的元素,也就是 \(\begin{bmatrix}1&1\end{bmatrix}\times \begin{bmatrix}1&1\\1&0\end{bmatrix}^{n-2}\) 的第一行第一列的元素。

注意矩阵乘法不满足交换律,所以不能将两个矩阵反过来,另外,对于 \(n\le 2\) 的情况,可以直接输出 \(1\)

P1962斐波那契数列 - 洛谷

参考代码:

#include<bits/stdc++.h>
#define int long long
#define P 1000000007
#define N 110
using namespace std;
int n;
struct sb{int m[N][N];}ans,base;
inline sb cheng(sb a,sb b,int ok)
{
    sb c;
    for(int i=1;i<=ok;i++)
    {
          for(int j=1;j<=ok;j++)
          {
              c.m[i][j]=0;
            for(int k=1;k<=ok;k++)
              c.m[i][j]=(c.m[i][j]+a.m[i][k]*b.m[k][j])%P;
          }
    }
    return c;
}
inline sb jzksm(sb x,int y)
{
    sb res=x;y--;
    while(y)
    {
        if(y&1)res=cheng(res,x,2);
        x=cheng(x,x,2);
        y>>=1;
    }
    return res;
}
signed main()
{
    cin>>n;
    if(n==1||n==2){puts("1");return 0;}
    ans.m[1][1]=1;ans.m[1][2]=1;
    base.m[1][1]=1;base.m[1][2]=1;
    base.m[2][1]=1;base.m[2][2]=0;
    base=jzksm(base,n-2);
    ans.m[1][1]=(base.m[1][1]+base.m[1][2])%P;
    cout<<ans.m[1][1]<<endl;
    return 0;
}

大部分内容来自 OI Wiki。

这篇关于矩阵入门的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!