論如何求矩陣的逆?先看看基礎芝士!

這是關於矩陣的一個bugblog

(若是以爲格式看不下去請移步:洛咕html

矩陣求逆是個有趣(但暫且不知道有什麼神奇運用)的東西,ios

不過矩陣這玩意兒貌似和線性代數以及向量之類的東西相關,因此學了絕對不虧
xiao
另外,本篇blog 並不必定毫無錯誤,甚至可能會有些理解上的小誤差,因此請各位觀看的神仙及時指出好讓做者修改精進,謝謝。c++

還有矩陣求逆的兩種方法將會放在最後講解git




想要學會矩陣求逆的話,首先你得了解幾個關於矩陣的概念以及名稱的含義算法

(固然若是你知道這些概念的話能夠直接往下跳)數組

基本概念

1.矩陣以及矩陣的階

下圖是一個三階矩陣:ide

\[[\begin{matrix}1&2&3\\4&5&6\\7&8&9\end{matrix}]\]優化

注意,這裏兩邊的中括號包含全部的 \(9\) 個數字。spa

(即左括號與\(1\)\(4\)\(7\)同高,右括號與\(3\)\(6\)\(9\)同高,下同)code

【在洛谷用 \(LaTeX\) 表示矩陣我最多也就作成這樣了 _(:з」∠)_

從這個例子中咱們能夠看出:一個** 3 行 3 列的矩陣叫作 3 階矩陣**

具體來說咱們管 \(n*n\) 的矩陣叫作 \(n\) 階矩陣

(下文逆矩陣部分中不加說明的通常就是 \(n\) 階的矩陣)

但若是有一個矩陣是 \(2*3\) 的,那麼咱們稱它爲 \(2*3\) 階矩陣

更具體的說,對於 \(n*m\ (n\not=m)\)的矩陣,咱們稱它爲 \(n*m\) 階矩陣

2. 矩陣的通常運算(一)【矩陣加法】

對於 \(A\)\(B\) 兩個矩陣相加,即矩陣 \(A+B\) ,這個運算的結果就是全部位置上的數一 一對應相加後所獲得的矩陣。

那麼 \(A\)\(B\) 兩個矩陣能夠進行加法運算的條件就是兩矩陣同階(關於階的概念就在上面)

舉個例子:

\[[\begin{matrix}1&2&3\\4&5&6\\7&8&9\end{matrix}] + [\begin{matrix}2&3&5\\6&7&6\\8&3&2\end{matrix}]=[\begin{matrix}1+2&2+3&3+5\\4+6&5+7&6+6\\7+8&8+3&9+2\end{matrix}]=[\begin{matrix}3&5&8\\10&12&12\\15&11&11\end{matrix}]\]

能夠看出,這就是簡單的加法。

文字描述一下就是:

咱們首先將將兩個矩陣全部位置一 一對應,
而後咱們將各個位置上的兩個數相加,
最後將獲得的值放回原位置,造成新的矩陣,

而且咱們能夠獲得幾個樸素的性質:

1.結合律: \(A+(B+C)=(A+B)+C\)

2.交換律: \(A+B=B+A\)

而後矩陣加法相關的題,我還真沒見到過...

至於矩陣減法的運算也與加法相似

3.矩陣的通常運算(二)【矩陣乘法】

這玩意兒詳細講的話又能夠寫一篇博客了。(好比什麼快速冪啊、構造矩陣啊、遞推加速啊還有一堆習題什麼的。。。)

因而這裏我就講講一點概念。

首先, \(A*B\) 不是隨便就能乘的,是要有條件的(和加法相似)

兩個矩陣 \(A\)\(B\) 能夠進行乘法運算的條件就是** \(A\) 的列數與 \(B\) 的行數相等**

舉個例子(\(2*2\)\(\times\) \(2*2\)階 的矩乘好了):
\[[\begin{matrix}1&2\\4&3\end{matrix}] \times [\begin{matrix}5&3\\2&1\end{matrix}]=[\begin{matrix}1*5+2*2&1*3+2*1\\4*5+3*2&4*3+3*1\end{matrix}]=[\begin{matrix}9&5\\26&15\end{matrix}]\]

然鵝這個例子並不能清晰的看出運算法則,因而咱們用字母代替數字:

\[[\begin{matrix}a&b\\c&d\end{matrix}] \times [\begin{matrix}e&f\\g&h\end{matrix}]=[\begin{matrix}a*e+b*g&a*f+b*h\\c*e+d*g&c*f+d*h\end{matrix}]\]

那麼用文字描述的話就是:

咱們選定矩陣 A 的第 i 行以及矩陣 B 的第 j 列,
將他們取出來一 一相乘獲得一個值,
而後將該值做爲結果矩陣第 i 行第 j 列的值,
重複以上步驟後咱們最終就能夠獲得一個新矩陣

此外,矩陣乘法也知足一些性質:

1.結合律: \(A*(B*C)=(A*B)*C\)

2.交換律(不知足): \(A*B != B*A\)

爲何不知足交換律?證實很簡單。
一個\(2*3\)階矩陣\(\times\) \(3*2\)階的矩陣結果是\(2*2\)階的矩陣,
可是交換後呢?結果就變成了\(3*3\)階的矩陣,連階數都不同了

3.分配律: \(A*(B+C)=A*B+A*C\)

分配律的證實不難(甚至能夠硬推),就留給讀者本身證實了

那麼矩乘有什麼意義?你問我我問誰

咳咳。這裏就涉及到了另外一個芝士:多維向量的乘積能夠由矩乘運算獲得

此外,矩乘還有一些其餘的實際應用吧,這裏咱們就不作深究了

至於矩乘的代碼呢,仍是常常要用的,因而下面給出(結構體代碼)

struct matrix{ ll a[M][M];
    ll* operator [](int x){ return a[x]; }
    matrix operator *(matrix& b){
        static matrix c;
        for(int i=1;i<=3;++i)
            for(int j=1;j<=3;++j)
                c[i][j]=0;
        for(int i=1;i<=3;++i)
            for(int j=1;j<=3;++j)
                for(int k=1;k<=3;++k)
                    c[i][j]+=a[i][k]*b[k][j],c[i][j]%=mod;
        return c;
    }
    inline void print(){
        for(int i=1;i<=n;++i,putchar('\n'))
            for(int j=1;j<=n;++j,putchar(' '))
                putchar('0'+a[i][j]);
    }
}F,T;

emmm...這裏扯點別的,就是關於 [ ] 的重載

這裏重載[ ] 以後,咱們就能夠直接像用數組同樣地調用結構體了

好比 原來的 \(F.a[i][j]\) 咱們能夠直接寫成 \(F[i][j]\)

但若是結構體裏面的數組 \(a\) 是一維的話,重載就要變成如下格式:

ll& operator [](int x){ return a[x]; }

這裏其實就是 \(*\) 改爲了 \(\&\)

至於多維的數組,那就是將單個 \(*\) 號改作 (維數\(-1\)) 個 \(*\) ,好比三維的數組就是寫做 \(ll^{**}\)

那麼學會告終構體封裝矩乘後,咱們的快速冪就基本是和日常的快速冪同樣打的,代碼也再也不給出

第二種矩陣乘法:矩陣的數乘

這裏還有一種矩陣乘法叫作數乘,就是一個實數乘以一個矩陣,結果其實就是矩陣中的每一個元素乘上這個常數,這裏就不詳解了


矩乘講完了。\(but\),說完乘法你是否是想到了除法?emmm...

矩除?不存在的。最可能是乘以它的逆矩陣?

因而下面進入逆矩陣部分了


4.單位矩陣

咱們將單位矩陣寫做 E ,而且單位矩陣都是 \(n\) 階矩陣

好比一個 \(3\) 階的單位矩陣長這樣:

\[[\begin{matrix}1&0&0\\0&1&0\\0&0&1\end{matrix}]\]

如上,單位矩陣就長這樣。

那麼單位矩陣知足什麼性質呢?

1.一個矩陣乘上單位矩陣結果是它自己: \(A*E=A\)

emmm...就這一個性質比較常見。你能夠將單位矩陣類比天然數 1 。

5.逆矩陣

對於一個矩陣 \(A\) ,咱們將它的逆矩陣寫做 \(A'\) 或者 \(A^{-1}\)

那麼逆矩陣知足什麼性質?

1.矩陣 \(A\) \(\times\) 它的逆矩陣 \(A^{-1}\) 的結果是單位矩陣 E : \(A*A^{-1}=E\)

從這裏咱們能夠看出,逆矩陣與乘法逆元倒有些類似之處

其實逆矩陣還有一個性質,等下就會講到了。

emmm...本來是本博客重頭戲的逆矩陣,篇幅就這麼短...還給本身當廣告位了

6.階梯型矩陣

階梯型矩陣其實就是一個矩陣 \(A\) 通過矩陣初等變換的洗禮後獲得梯形矩陣

(其實上面是通常矩陣的階梯型矩陣定義,對於 n 階矩陣,它的階梯型矩陣通常是一個上三角...)

階梯型矩陣知足條件:每行的不爲零的首元按標號呈升序。(百科上的,貌似也不難理解)

舉個例子:

\[[\begin{matrix}9&5&3&4\\0&6&1&5\\0&0&3&2\\0&0&0&8\end{matrix}]\]

注意,階梯型矩陣每行首元不必定爲 \(1\) ,更沒有強制爲 \(1\) ,每行首元爲 \(1\) 的矩陣並非階梯型矩陣,僅僅是高斯消元中用來求方程組的解所用的矩陣,首元爲 \(1\) 是爲了方便求解,請讀者不要將兩個東西混爲一談,真正的階梯型矩陣僅使用矩陣初等變換便可解出,這裏稍微提一下。

那麼階梯型矩陣怎麼求呢? \(that's a question.\)

我只能說,瞭解一下高斯消元吧!【模板】高斯消元法:點這裏

儘管上面說了,階梯型矩陣嚴格來說並非經過高斯消元求解的,可是與高斯消元的仍是有不少類似的運算的

7.矩陣的秩

幾何意義上來說(其實就是線性代數角度吧?),一個矩陣的秩(rank)表明這個矩陣可以跨越的維度數(沒錯,就是說秩爲 \(x\) 的矩陣經過每一個元素乘以一個實數後能夠表示任意的 \(x\) 維向量

矩陣的秩與線性相關這一律念聯繫很是大

這裏有兩句關於行秩和列秩的解釋:

一個矩陣A的列秩是A的線性獨立的縱列的極大數
一個矩陣A的行秩是A的線性獨立的橫行的極大數

emmm...百度百科貌似都是寫給懂的人看的(誰 \(TM\) 懂線性相關還不會矩陣的啊!)

行秩和列秩的話,通俗來說是這樣的:

將矩陣作初等行變換後,非零行的個數叫行秩
將其進行初等列變換後,非零列的個數叫列秩

那麼你能夠理解爲一個矩陣的階梯型矩陣非零行數 就是行秩,列秩同理(階梯型矩陣上文剛剛講完)。

那麼咱們能夠看出,一個矩陣的行秩與列秩必然相等。

爲何?你試試將一個矩陣對角線翻轉一下不就行了?(哎呀這不是矩陣的轉置麼,等會兒會講的)

假如說一個矩陣的秩等於它的階,那麼就說這個矩陣滿秩

那麼若是一個矩陣的秩小於他的階,那麼這個矩陣被稱爲奇異矩陣

因此若是一個矩陣的秩大於它的階呢?咳咳,這種狀況不存在,秩數最多等於階數

那麼如何計算一個矩陣的秩數?咱們能夠用定義法求解,就是像上面說的將原矩陣化做階梯型矩陣而後計算出非零行數

但若是是小數據的人工計算的話,你能夠依據矩陣中有多少行不能由矩陣的其餘行變換獲得,來求解矩陣的秩(這不是和求解階梯型矩陣差很少麼)
舉個例子:

\[[\begin{matrix}1&2&3\\2&2&4\\5&6&11\end{matrix}]\]

這個矩陣中的第三行就能夠經過:(第一行\(\times 1\) + 第二行 \(\times 2\)) 獲得

\[[\begin{matrix}1&2&3\\2&2&4\\0&0&0\end{matrix}]\]

若是一個矩陣長上面這樣,那麼這個矩陣的第三行其實也是能夠經過其餘行乘上 \(0\) 獲得的,因此這個矩陣是線性相關

這麼講,若是矩陣的某一行能夠由其餘行拼湊而成的話,那其實咱們能夠看出這一行元素在線代角度看對更高維向量的表示是沒有用的,由於它能夠由其餘行元素拼出來,沒有什麼存在的價值

這也就是線性相關的大體概念了(話說線性基裏面好像也有線性相關來着,若是你會線性基理解起來會輕鬆不少吧)

那若是我只是判斷當前矩陣是否滿秩,或者是不是奇異矩陣呢?這樣的話,咱們除了上面直接把秩數求出來以外,還有一種方法,就是求行列式,關於行列式的求解就在下一節。

8.矩陣的行列式

對於一個矩陣 \(A\) ,它的行列式記做 \(|A|\)

舉個例子,3階的行列式的運算是這樣的:
\[|A|=|\begin{matrix}a&b&c\\d&e&f\\g&h&i\end{matrix}|\]

行列式有着它的幾何意義(仍是線性代數上的),
就是說 某個矩陣 乘上矩陣 \(A\) ,其有向體積將會擴大爲原來的 \(|A|\)(準確來講不能講體積,這個我也不知如何形容,請感性理解)

而後注意這裏的體積是有向的(就是說體積有可能神奇的取到負值)。

舉個例子,一個 \(2\) 階矩陣 \(A\) 的行列式是 \(|A|\),那麼某個矩陣乘上 \(A\)(固然是知足相乘條件的狀況下),這個矩陣在線性代數的平面內表示的面積將會擴大 \(|A|\) 倍(也就是面積乘上了\(|A|\)

至於 \(3\) 階矩陣 \(A\) ,某矩陣乘上它後,體積就會擴大 \(|A|\) 倍。

還有行列式的用途,這個其實就是上面說起的,判別矩陣是否滿秩(或者是不是奇異矩陣

那麼如何計算矩陣 \(A\) 的行列式?

這個嘛...意會了就以爲不是很難,這裏給出兩個例子聰明的你應該就能看懂了

eg1:\[ |A|=|\begin{matrix}a&b\\c&d\end{matrix}|=ad-bc\]

二階行列式就是左下右上減右上左下嘛...

這個二階的行列式運算仍是蠻有意義的(前方線代高能預警!)

其實這個運算就至關於兩個二維向量的叉積

所謂叉積的幾何意義其實就是兩個向量在二維平面上圍出的平行四邊形的面積,這個看完視頻很快就會懂了

eg2:\[|A|=|\begin{matrix}a&b&c\\d&e&f\\g&h&i\end{matrix}|=a(ei-fh)-b(di-fg)+c(dh-eg)\]

<1>行列展開

那麼在這裏,我是用行列展開求解的。具體的運算方法獲得餘子矩陣那裏講(額,劇透了)

不過你能夠先看看圖解:

..........偷來的QvQ..........

<2>對角線法則

emmm...對於三階矩陣的行列式運算,還有一種方法,叫作對角線法則,但很遺憾,對角線法則僅適用於2、三階矩陣的行列式運算。

下圖就是對角線法則的圖解,其中實線是加,虛線是減:

偷來的QvQ

而後求解行列式的話通常來講最多也就讓你求二階或者三階的,階再高的話就是計算機的事兒了

另外講講同窗說的作法,\(TA\) 的作法是這樣的:

<3>逆序定義法

考慮每行取一個元素,且最終取出的 \(n\) 個元素任意兩個不在同一列,而後這 \(n\) 個數相乘,以後就是一些加減運算了,至於對行列式貢獻的正負性,貌似是選出元素相對位置逆序對個數奇偶性決定的,咳咳。。。

而後下面是圖解:

固然咱們能夠看出這個方法複雜度有點高,是 \(O(n!\times n \log n)\) 的,因此這是人工求的時候纔可能採用的方法。

<4>初等行列變換

以前說矩陣的時候有講到過這個東西,在這裏的做用其實也就是將原矩陣轉換成一個上三角矩陣(沒錯,仍是上文說的階梯型矩陣!),當咱們求出了這個上三角矩陣(階梯型矩陣)對角線上全部元素的乘積就是原矩陣的行列式值了

這裏說是初等行列變換,但網上都說這是高斯消元求解行列式,emmm...可是行列式的計算不能直接用到數乘的啊!

若是要問爲何的,你能夠想一想,假如是一階矩陣(不爲 \(0\)),那麼它隨便乘上一個數,行列式就已經改變了,因此單一行的轉換是不能用到數乘的。

因此請注意:初等行列變換不包含數乘(關於矩陣數乘的概念在第二節中已經講過了)

那麼這個高斯消元法行列變換的算法就不詳細講解了,讀者能夠到這個博客裏面去看,這個做者已經寫的很詳細了,另外那些性質什麼的這個博客裏面也有(甚至逆序對求法也講了一丟丟),本博客就再也不給出,以節省博客篇幅主要是做者懶

而後這個算法的複雜度? \(O(n^{3})\) 吧...

還有,行列式這塊對於矩陣比較重要,因此建議這個東西要至少理解七八成。

鑑於做者有自知之明,知道本身講的確定沒有那麼生動(畢竟是文字描述),那麼就附上一個 B 站上的視頻,好讓讀者們更好理解行列式:

視頻點這裏

而對於以前提到的秩呢,這個概念一樣很重\((nan)\)\((dong)\),畢竟是和線性相關有聯繫的,因此也附上個 B 站上的視頻:

視頻點這裏

而後這倆視頻是在一個系列裏面的,因此別的部分的內容你也能夠看看(順便練練英語聽力),總之建議把整個系列都看完啦~


記得想學伴隨矩陣的求法時看到了一個「矩陣三連」,特別有意思:伴隨矩陣是餘子矩陣的轉置矩陣

\(woc\),三個裏面沒有一個會的,怎麼辦,我還有救麼...(這時候好想開個\(Venus\)三連:跳起來,敵敵畏,膜膜膜

咳咳,以後我就瞭解了下這三個矩陣的概念。


9.轉置矩陣

咱們將矩陣** \(A\) 的轉置矩陣寫做 \(A^{T}\)**

轉置矩陣這玩意兒其實很是好理解。

一個矩陣的轉置矩陣其實就是它 \((i,j)\) 位置上的數與 \((j,i)\) 位置上的數交換了一下。

舉個例子:

\[[\begin{matrix}6&3&5&4\\2&6&1&3\\3&7&1&2\\2&4&4&7\end{matrix}]\]

轉置以後是這樣的:

\[[\begin{matrix}6&2&3&2\\3&6&7&4\\5&1&1&4\\4&3&2&7\end{matrix}]\]

仍是不明顯的話,用字母表示一下:

\[[\begin{matrix}a&b&c&d\\e&f&g&h\\i&j&k&l\\m&n&o&p\end{matrix}]\]

通過轉置:

\[[\begin{matrix}a&e&i&m\\b&f&j&n\\c&g&k&o\\d&h&l&p\end{matrix}]\]

這下咱們就能夠清楚的看到:轉置矩陣其實就是將原矩陣左下、右上對角線翻轉了一下罷了。

因此矩陣轉置完有什麼用?我也想知道啊!

結論:矩陣轉置是用來作題的,只對矩陣各類轉換有用,因而不用在乎它的意義

此外,一個** \(n*m\) 階的普通矩陣的轉置也是右上左下翻轉,轉置後的矩陣爲一個 \(m*n\) 階的矩陣**

10.伴隨矩陣

咱們將矩陣** \(A\) 的伴隨矩陣寫做 \(A^{*}\)** (不是那個搜索算法啦 【笑哭)

但對於這玩意兒我只知道它的定義以及一些性質,至於求法?emmm...

一個矩陣 \(A\) 的伴隨矩陣要知足一下性質:

\[A\times A^{*}=|A|\times E\]

就是說 \(A\) 乘上它的伴隨矩陣 \(A^{*}\) 以後,結果是單位矩陣 \(E\)\(|A|\)

其次,伴隨還知足一系列性質:

1.如上,\(A\times A^{*}=|A|\times E\)

2.\(A\) 的伴隨矩陣爲其逆矩陣的 \(|A|\) 倍: \(A^{*}=|A|\times A^{-1}\)

3.原矩陣和伴隨矩陣之間秩的關係:

\(rank(A) = n\) 時, \(rank(A^{*}) = n\)

\(rank(A) = n-1\) 時, \(rank(A^{*}) = 1\)

\(rank(A) < n-1\) 時, \(rank(A^{*}) = 0\)

4.原矩陣和伴隨矩陣之間行列式的關係: \(|A^{*}|=|A|^{n-1}\)

5.當原矩陣可逆時,伴隨矩陣可逆:\(?B=A^{-1} ? ?C=(A^{*})^{-1}\)

第二個性質就是以前講逆矩陣的時候沒說說的性質,這個性質很好證吧?由於有第一個性質啊!

第三個性質,死記吧...

第五個性質的話能夠運用第三個性質證實...滿秩的矩陣確定存在逆矩陣嘛...

emmm...其實這裏是有證實的啦:

\(A^{-1}=\dfrac{A^{*}}{|A|}\)

\(|A^{-1}|=|\dfrac{A^{*}}{|A|}|\)

\(\dfrac{1}{|A|}=(\dfrac{1}{|A|})^{n}\times |A^{*}|\) (在這裏第四個性質已經證實出來了)

$|A^{}| \not= 0 ? ?C = (A^{})^{-1} $

emmm...這裏咱們考慮第三步是怎麼轉換到第四步的:

其實很簡單,咱們考慮 \(\dfrac{1}{|A|}\)\(|A^{*}|\) 的貢獻,

在這裏,\(\dfrac{1}{|A|}\) 使得 \(|A^{*}|\) 縮小了多少?

emmm...考慮行列式的計算時這個值產生的貢獻

其實就是減小到了原來的 \((\dfrac{1}{|A|})^{n}\)

嚴謹地說,就是考慮 \(A^{*}\) 的每一個元素都乘上了 \(\dfrac{1}{|A|}\),那麼在計算\(\dfrac{A^{*}}{|A|}\) 這個新矩陣的行列式時,考慮 8.行列式 中所言,\(\dfrac{1}{|A|}\) 在每一項成績中都計算了 \(n\) 次,因此最終 \(A^{*}\)的行列式減少到了原來的 \((\dfrac{1}{|A|})^{n}\)

不過這裏仍是有個性質沒講,就是伴隨矩陣是餘子矩陣的轉置矩陣什麼的,但這個不重要,學完餘子矩陣以後你強行代數也可證實。

回到以前說的,伴隨矩陣的求法。這個的話,你會逆矩陣的求法就能解出伴隨矩陣了(不難懂吧)

但還有另外一個求法,不過要用代數餘子式求,那就是接下來的內容了

11.(代數)餘子式與(代數)餘子矩陣

其實你看懂了行列式的話這裏就很好理解了。

首先,餘子式是對於矩陣 \(A\) 中的一個元素 \(A_{i,j}\) 而言的一個數值(標量),而餘子矩陣則是** \(A\) 中全部元素的餘子式**所構成的一個新矩陣。

那麼如何計算矩陣元素 \(A_{i,j}\) 的餘子式呢?

方法就是: 先將原矩陣 \(A\) 的第 \(i\) 行、第 \(j\) 列消掉,而後求剩下的 \(n-1\) 階矩陣的行列式,這樣咱們就獲得了 \(A_{i,j}\) 的餘子式,這時若是將這個元素的餘子式乘上 \((-1)^{i+j}\times A_{i,j}\) ,求得的值就是 \(A_{i,j}\)代數餘子式,而且由原矩陣中全部元素代數餘子式構成的矩陣就是原矩陣的代數餘子式矩陣(以前說餘子矩陣能夠認爲是這玩意兒的縮寫),而它的轉置矩陣就是上一節說的伴隨矩陣

可是證實呢?爲何餘子式矩陣的轉置矩陣就是伴隨矩陣了?這個你能夠用定義來解:

因爲行列式等於某一行全部元素與其對應代數餘子式的乘積之和

又由於伴隨矩陣的定義就是與原矩陣相乘結果爲單位矩陣的行列式倍,

因此這時咱們能夠將餘子矩陣轉置一下,讓它的任意一行元素以列的方式排布

而後就能夠和原矩陣愉快地乘起來,獲得定義中的單位矩陣的行列式倍的矩陣了。

至於更加詳細的證實推導,就留給讀者吧

emmm...因此這就是上文伴隨矩陣中說的另外一種求法啦

那麼在 8.行列式 中做者提到的行列展開其實就是用到了代數餘子式這個東西,畢竟2階矩陣的行列式仍是好算的嘛

可是爲何代數餘子式可以用來求行列式呢?這是個問題,但做者太菜瞭解決不了,若是你感興趣的話能夠花些時間強行代數證實一下加深影響,或者就是背個結論就行了

從上面咱們能夠看出,用代數餘子式求解行列式是很是很是煩的一件事(雖然說對於小型矩陣尤爲是三階的仍是很方便的),這個算法的複雜度已經高達 \(n!\) 了!因此通常來說用初等行列變換求行列式纔是明智的選擇。

不過這個方法仍是能夠用來娛樂一下的,好比這裏有一份網上來的代碼,我就加了點常數優化什麼的。

//by Judge 
#include<bits/stdc++.h>
using namespace std;
int **array = NULL,m;
int** pArray(int **array, int m) {
    for (int i = 0,j; i < m; ++i,puts("||"))
        for (printf("||")j = 0; j < m; ++j)
            printf("%d\t", array[i][j]);
    return array;
}
int** inputArray(int** array, int m) {
    for (int i = 0,j; i < m; ++i,puts("||"))
        for (printf("||"),j = 0; j < m; ++j)
            cin >> array[i][j];
    return array;
}
double calMatrix(int ** temp, int m) {
    int **Matrix = NULL,i; double result=0;
    if (m == 1) return temp[0][0];
    if (m == 2) return temp[0][0]*temp[1][1]-temp[0][1]*temp[1][0];
    for (i = 0; i < m; ++i) {
        Matrix = (int**)malloc(sizeof(int *)*(m-1));
        for (int k = 0; k < m - 1; ++k)
            Matrix[k] = (int *)malloc(sizeof(int)*(m - 1));
        for (int k = 1,a=0; k < m&&a<m-1; ++k,++a)
            for (int j = 0,b=0; j < m&&b<m-1; ++j)
                if(i^j) Matrix[a][b++] = temp[k][j];
        result+=temp[0][i]*pow(-1,i)*calMatrix(Matrix,m-1);
    } return result;
}
int main() {
    printf("please input the matrix order:\n");//輸入矩陣階數
    cin >> m,array = (int**)malloc(sizeof(int*)*m);
    for (int i = 0; i < m; ++i)
        array[i] = (int*)malloc(sizeof(int)*m);
    printf("input the array:\n");
    inputArray(array, m);
//  printf("the array you input:\n"); //輸出輸入的矩陣 
//  pArray(array, m),puts("");
    puts("the value of the matrix is: ");
    cout<<calMatrix(array, m)<<endl; //輸出行列式 
    return system("pause"),0;
}

有點工程級別的感受...

話說這個僅供娛樂(千萬別拿去交了,交了八成就是 \(T\) 飛)


而後這些基礎知識就大概是告一段落了,接下來就愉快的開始矩陣求逆啦~


重頭戲 · 矩陣求逆

方法一:高斯消元構造逆矩陣

這個方法很是的常見,矩陣求逆板子題題解裏隨便抽一篇就是高斯解法

實際上的作法(以及原理)上面也說了一半多了,求解的第一步就是將原矩陣化成階梯型矩陣,而後仍是各類樸素運算將消成單位矩陣

就直接先上例子好了,假設咱們要求逆的矩陣以下:

\[[\begin{matrix}2&2&3\\1&-1&0\\-1&2&1\end{matrix}]\]

首先咱們將原矩陣與一個單位矩陣放在一塊兒:(爲了看的清楚一點,我將兩個矩陣分開了一點)

\[[\begin{matrix}2&2&3&&1&0&0\\1&-1&0&&0&1&0\\-1&2&1&&0&0&1\end{matrix}]\]

而後咱們方便起見能夠將第一行和第二先換一下:(注意,這裏和以前提到高斯消元求法不同,如今交換了的兩行最後不須要換回來)

\[[\begin{matrix}1&-1&0&&0&1&0\\2&2&3&&1&0&0\\-1&2&1&&0&0&1\end{matrix}]\]

接着咱們經過初等行列變換讓第二行和第三行的首個元素變爲 \(0\)

\[[\begin{matrix}1&-1&0&&0&1&0\\0&4&3&&1&-2&0\\0&1&1&&0&1&1\end{matrix} ]\]

以後咱們發現將第三行和第二行交換後更利於計算,那就交換一下:

\[[\begin{matrix}1&-1&0&&0&1&0\\0&1&1&&0&1&1\\0&4&3&&1&-2&0\end{matrix}]\]

而後用初等變換繼續消元:

\[[\begin{matrix}1&-1&0&&0&1&0\\0&1&1&&0&1&1\\0&0&-1&&1&-6&-4\end{matrix}]\]

而後第三行乘上\(-1\):(話說以前不是說只能初等變換的麼...額,這裏不同,你這麼理解吧,畢竟咱們的目的是將當前矩陣求逆而不是求行列式,性質不同的)

\[[\begin{matrix}1&-1&0&&0&1&0\\0&1&1&&0&1&1\\0&0&1&&-1&6&4\end{matrix}]\]

如今咱們已經把各行首元變爲 \(0\) 了,接下來的任務是處理左部分矩陣的上三角

那麼咱們用第二行消去第一行的第二個元素:(在這裏就是第一行加上第二行)

\[[\begin{matrix}1&0&1&&0&2&1\\0&1&1&&0&1&1\\0&0&1&&-1&6&4\end{matrix}]\]

第三列的元素處理也是同樣的道理:(用第三行來減)

\[[\begin{matrix}1&0&0&&1&-4&-3\\0&1&0&&1&-5&-3\\0&0&1&&-1&6&4\end{matrix}]\]

至此,咱們成功的將左部分矩陣轉換成了一個單位矩陣。因此,逆矩陣呢?

逆矩陣就是右部分矩陣。

確實是這樣啊,emmm...不信的話本身檢驗一下嘛,矩乘也蠻快的

至於爲何會這樣呢?

由於對矩陣 \(A\) 進行行初等變換,就至關於左乘以一和初等矩陣(其實你大可沒必要在乎這個矩陣的含義,由於我也不打算詳細講,不過講道理其實就是與原矩陣相乘後結果爲單位矩陣的矩陣),對A進行初等變換,也就至關於右邊乘以一個相同的這個初等矩陣,而後咱們考慮左邊的矩陣變換以後成了單位矩陣,那麼這時候左邊乘上原矩陣 \(A\) 以後變回了原矩陣,因此右邊矩陣乘上 \(A\) 以後也會變回原來的樣子,也就是 一個單位矩陣 \(E\) ,那麼就知足條件: \(A\times A^{-1}=E\) 了,因此右邊的矩陣就是 \(A\) 的逆矩陣

可是請注意,一個矩陣的逆矩陣不必定每一個元素都是整數,甚至每每是有理數(因此 \(bzt\) 裏讓你求取模意義下的逆矩陣,避免一些精度問題)

因而默默放上代碼:(又是加工過的「工程級」代碼)

//by Judge
#include <iostream>
#include <stdlib.h>
using namespace std;
int n; static double a[50][50],b[50][50];
//交換當前行與下一行
void exchange(double a[][50],double b[][50],int current_line,int next_line,int all_line_number) {
    //交換兩行
    int cl=current_line,nl=next_line,n=all_line_number;
    for(int i=0; i<n; i++)
        swap(a[cl][i],a[nl][i]),
        swap(b[cl][i],b[nl][i]); 
}
void the_data_change_to_1(double a[][50],double b[][50],int m,int n) { //將a[m][m]變成1
    if(a[m][m]) {
        for(int i=m+1; i<n; ++i) a[m][i]=a[m][i]/a[m][m];
        for(int i=0; i<n; ++i) b[m][i]=b[m][i]/a[m][m];
        return (void)(a[m][m]=1); //change_to_upper_angle_matrix(a,b,m,n);//將a[m][m]之下的元素所有變成0
    }
    while(m+1<n&&!a[m][m]) exchange(a,b,m,m+1,n);
    if(a[m][m]!=1) the_data_change_to_1(a,b,m,n);
}
//將a[m][m]之下的元素所有變成0
void change_to_upper_angle_matrix(double a[][50],double b[][50],int m,int n) {
    if(m+1>=n) return ;
    for(int i=m+1,t; i<n; ++i,a[i][m]=0) { t=a[i][m];
        for(int j=m; j<n; ++j) a[i][j]=a[i][j]-t*a[m][j];
        for(int k=0; k<n; ++k) b[i][k]=b[i][k]-t*b[m][k];
    }
}
/*將上三角矩陣變成單位陣*/
void change_to_unit_matrix(double a[][50],double b[][50],int l,int n) {
    if(l<=0) return ;
    for(int i=l-1; i>=0; a[i][l]=0,--i) //從a[l-1][l]開始向上,讓每一個元素都變成0
        for(int j=0; j<n; ++j)
            b[i][j]=b[i][j]-a[i][l]*b[l][j];
    --l,change_to_unit_matrix(a,b,l,n);
}
//打印結果
void print_result(double b[][50],int n) {
    for(int i=0; i<n; ++i,cout<<endl)
        for(int j=0; j<n; j++) cout<<b[i][j]<<" ";
}
int main() { cin>>n;
    for(int i=0; i<n; ++i)
        for(int j=0; j<n; ++j)
            scanf("%lf",a[i]+j);
    for(int i=0; i<n; ++i) b[i][i]=1;
    for(int i=0; i<n; ++i) {
        if(a[i][i]!=1) the_data_change_to_1(a,b,i,n);//將a[i][i]變成 1
        if(a[i][i]==1) change_to_upper_angle_matrix(a,b,i,n); //將a[i][i]之下的元素所有變成 0
    }
    change_to_unit_matrix(a,b,n-1,n);
    return print_result(b,n),0;
}

上面這個代碼是直接實數求解的,可是若是你不嫌煩的話,能夠考慮用分數形式表示逆矩陣,(也並非作不到)但這裏就再也不給出了

另外,用高斯解的方法仍是蠻常規的,因此通常來說咱們都用這個方法來求解,至於接下來那個看看就差很少了

方法二:解方程組構造(非同階)逆矩陣

相信你可能想到過一下算法:

咱們能夠嘗試依照矩乘的定義來設未知數求解逆矩陣

拿方法二中的矩陣做例:

原矩陣是這樣的:

\[[\begin{matrix}2&2&3\\1&-1&0\\-1&2&1\end{matrix}]\]

咱們直接設一個 \(3\) 階的矩陣,矩陣中的全部元素都未知:

\[[\begin{matrix}a&b&c\\d&e&f\\g&h&i\end{matrix}]\]

那麼咱們能夠像以前說的,又矩乘定義獲得 \(3*3=9\) 個方程:(不想打了好累啊

\[[\begin{matrix}2*a+2*d+3*g=1\\2*b+2*e+3*h=0\\2*c+2*f+3*i=0\\......\\(-1)*c+2*f+1*i=1\end{matrix}]\]

而後咱們愉快的解方程。沒錯,仍是用高斯消元解!

從上面的算法流程咱們能夠看出,這個算法沒毛病!然鵝提及它的複雜度那就真的過高了,足足\(O(n^{4})\)

雖然說複雜度是蠻高的,可是相較於前兩個算法,對於小規模數據的人工計算,這種算法也不失爲一個求逆矩陣的好方法

另外你應該看到了標題說的求非同階逆矩陣了吧?(其實講道理並非非同階的並不能說是逆矩陣)

其實非同階逆矩陣就是能夠用剛剛說的方法解的,方法也與上面差異不大,都是設矩陣求解未知數

非同階求逆矩陣的通常形式就是給出 \(n\) 階矩陣,求一個 \(n*m\) 階矩陣,要求與原矩陣乘積爲一個給定的 \(n*m\) 階矩陣

可是咱們通常來就就是求一個已知的 \(n\) 階矩陣乘上一個 \(n*1\) 階矩陣後的結果爲一個已知的 \(n*1\) 矩陣(好吧其實這就是求解多元方程組...)

不過若是你遇到了什麼亂七八糟的題目要你求這個東西的話,別忘了這種算法(複雜度\(O(n^{4})\)的渣渣算法)

方法三:我不會啊誰來教教我

首先具體求法以下:(題解裏剽來的)

①找到當前方陣(假設是第k個)的主元

②將主元所在行、列與第k行、第k列交換,而且記錄下交換的行和列

③把第k行第k個元素變成它的逆元(同時判無解:逆元不存在)

④更改當前行(乘上剛纔那個逆元)

⑤對矩陣中的其餘行作和高斯消元幾乎徹底同樣的操做,只有每行的第k列不同,具體看代碼

⑥最後把第②步中的交換逆序地交換回去。

emmm...這裏要稍微解釋一下,板子題裏說的逆矩陣是在模意義下的逆矩陣,並非實數意義下的逆矩陣,因此上面提到了逆元,(話說這份代碼比較難懂,由於它貌似還用了列的變換,更絕的是它還直接在原矩陣上求逆)還有關於實數下的逆矩陣求解也會在下面的下面給出

咳咳,若是下面的代碼三分鐘以內看不懂的話,直接跳過吧(由於這個作法的原理不知道是什麼...)

//by Judge(壓行狂魔沒辦法了, ctrl+shift+A 吧)
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
const int mod=1e9+7;
const int M=1e5+7;
#ifndef Judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
inline void cmax(int& a,int b){if(a<b)a=b;}
inline void cmin(int& a,int b){if(a>b)a=b;}
char buf[1<<21],*p1=buf,*p2=buf;
inline int read(){ int x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
} char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
inline void print(int x,char chr='\n'){
    if(C>1<<20)Ot(); while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]=chr;
} int n,is[405],js[405]; ll a[405][405];
inline ll qpow(ll x,int p){ ll s=1;
    for(;p;p>>=1,x=x*x%mod)
        if(p&1) s=s*x%mod; return s;
}
inline void inv(){
    for(int k=1;k<=n;++k){
        for(int i=k;i<=n;++i) for(int j=k;j<=n;++j)
            if(a[i][j]){is[k]=i,js[k]=j;break;}
        for(int i=1;i<=n;++i) swap(a[k][i],a[is[k]][i]);
        for(int i=1;i<=n;++i) swap(a[i][k],a[i][js[k]]);
        if(!a[k][k]){exit(!puts("No Solution"));}
        a[k][k]=qpow(a[k][k],mod-2);
        for(int j=1;j<=n;++j) if(j^k)
            (a[k][j]*=a[k][k])%=mod;
        for(int i=1;i<=n;++i) if(i^k)
            for(int j=1;j<=n;++j) if(j^k)
                (a[i][j]+=mod-a[i][k]*a[k][j]%mod)%=mod;
        for(int i=1;i<=n;++i) if(i^k)
            a[i][k]=(mod-a[i][k]*a[k][k]%mod)%mod;
    }
    for(int k=n;k;--k){
        for(int i=1;i<=n;++i)
            swap(a[js[k]][i],a[k][i]);
        for(int i=1;i<=n;++i)
            swap(a[i][is[k]],a[i][k]);
    }
}
int main(){ n=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            a[i][j]=read(); inv();
    for(int i=1;i<=n;print(a[i][n]),++i)
        for(int j=1;j<n;++j) print(a[i][j],' ');
    return Ot(),0;
}

這個方法雖然看不懂原理,可是很高效...那個大佬懂的教教我QAQ



從上面層層講解看來,矩陣和線性代數的關係很是大,而線性代數在大學中各個專業基本都是要掌握的,(計算機系還用說?)因此矩陣必定要學好啊

至此,矩陣求逆的基礎芝士已經講解完畢了,至於其餘關於矩陣的芝士就請讀者們自行探究吧

參考資料:實在太多記不清 _(:з」∠)_

相關文章
相關標籤/搜索