1. 程式人生 > >論如何求矩陣的逆?先看看基礎芝士!

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

這是關於矩陣的一個bugblog

(如果覺得格式看不下去請移步:洛咕

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

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

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




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

(當然如果你知道這些概念的話可以直接往下跳)

基本概念

1.矩陣以及矩陣的階

下圖是一個三階矩陣:

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

注意,這裡兩邊的中括號包含所有的 \(9\) 個數字。

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

【在洛谷用 \(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...對於三階矩陣的行列式運算,還有一種方法,叫做對角線法則,但很遺憾,對角線法則僅適用於二、三階矩陣的行列式運算。

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

偷來的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



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

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

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