1. 程式人生 > >高斯消元算法

高斯消元算法

比例 最後一行 提速 思考 dot strong 一個 times ref

高斯消元其實在算法競賽中算是一個十分常見的算法。它的大致思想就和初中階段學到的加減消元法差不多。這個算法的時間復雜度為\(O(n^3)\),是一個相當簡單的算法,但是具體實現需要一些思考。

這裏給出模板題的鏈接:
洛谷P3389
P4035


1.1 問題引入

給定方程組\(\begin{cases}x+3y+4z=5\quad(1)\\x+4y+7z=3\quad(2)\\9x+3y+2z=2\quad(3)\end{cases}\),求解之。

這是一個相當簡單的三元一次方程組,直接用加減消元就可以得出解。當然,這裏給出一個比較不討巧的消元順序,為了方便後面的理解。

首先(2)式減去(1)式:\(y+3z = -2\quad(2)'\)

接下來\(9\times(1)-(3)\):\(24y+34z=43\quad(3)'\)

此時\(y\)\(z\)就構成了二元一次方程組了。

計算\(24\times(2)'-(3)'\): \(38z=-91\).

解得\(z=-\frac{91}{38}\).此時不斷的回代可以得到\(y=\frac{197}{38}\), \(x=-\frac{37}{38}\).

我們可以把整個計算過程得到的新的方程式列出來:
\[\begin{cases}x+3y+4z=5\quad(1)\\y+3z=-2\quad(2)'\\38z=-91\quad(3)''\end{cases}\]

仔細觀察一下這個方程組,這對於之後的解題有幫助。

思考一下,對於任意的三元一次方程組,或者更進一步的,對於任意的\(n\)元一次方程組,我們能不能設計一個通用的算法,求解方程組呢?

1.2 初等列變換

我們把\(x\),\(y\),\(z\)的系數提取出來,將它們列成一個3*3的表:\(\begin{bmatrix}1&3&4\\1&4&7\\9&3&2\end{bmatrix}\)

我們還可以把方程的右側常數列在右邊:\(\begin{bmatrix}1&3&4&|5\\1&4&7&|3\\9&3&2&|2\end{bmatrix}\)


這個3*4的表格叫做方程組的增廣矩陣。規定矩陣的第\(i\)行為\(r_i\),如\(r_1=\begin{bmatrix}1&3&4&|5\end{bmatrix}\).
我們可以對它進行這樣幾個操作:

  • 用一個非零常數乘上某一行。這個操作記為\(r_i=cr_i\),其中\(c\)為非零常數
  • 把其中一行的若幹倍加至另一行上。這個操作記為\(r_i=cr_i+c'r_j\)
  • 交換兩行的位置。直接記作\(\text{swap}(r_i,r_j)\)就好了。

這樣一來,我們之前的操作可以這樣表示:
1.\(r_2=r_2-r_1\)
2.\(r_3=9r_1-r_3\)
3.\(r_3=24r_2-r_3\)

這個時候增廣矩陣變成了這個樣子:\(\begin{bmatrix}1&3&4&|5\\0&1&3&|-2\\0&0&38&|-91\end{bmatrix}\)

往往矩陣元素等於0時我們省略不寫,上面的矩陣還可以這樣寫:\(\begin{bmatrix}1&3&4&|5\\ &1&3&|-2\\ & &38&|-91\end{bmatrix}\)

排版有點醜,但是我們還是可以看出來,這個矩陣左邊的形狀有點像一個倒過來的階梯。人們就叫它簡化階梯型矩陣。把原增廣矩陣變為現在的簡化階梯型矩陣的過程就叫做高斯消元。
算出了簡化階梯型矩陣之後,直接一步一步回代就可以了。

1.3 基本實現

總結一下,高斯消元的思路:
對於每一行\(r_i\),我們要讓\(r_{i,i}=1\),並且\(r_{i,i}\)下面的元素均變為\(0\)。我們這樣做:

  1. 選取\(|r_{j,i}|\)最大的一行\(r_j\),將它與\(r_i\)交換。之後\(r_{i,i}\)就會變為當前列最大的元素。
  2. \(r_{i,i}\)變為\(1\)。很簡單,直接讓\(r_i=\frac{1}{r_{i,i}}r_i\)就行了。
  3. \(r_{i,i}\)下面的元素都變為零。對於\(i+1 \leq j \leq n\),我們讓\(r_j=r_j-\frac{r_{j,i}}{r_{i,i}}r_i\),調整\(r_i\)的比例並把\(r_{j,i}\)消去。因為\(r_{i,i}=1\),這個操作可以寫成\(r_j=r_j-r_{j,i}r_i\)就行了。

這一段的大概代碼如下():

RP(i,1,n)
    {
        rg int cur=i;
        RP(j,i+1,n)
        {
            if(fabs(r[cur][i])<fabs(r[j][i]))
            cur=j;
        }
        if(fabs(f[cur][i])<elp)
        {
            puts("No Solution");
            exit(0);
        }
        if(i!=cur) 
            swap(r[i],r[cur]);  
        RP(j,i,n+1)
            r[i][j]/=r[i][i];
        RP(j,i+1,n)
        {
            RP(k,i,n+1)
                r[j][k]-=r[i][k]*r[j][i];
        }
    }

在最後回代的過程中,由於最後一行的形式一定是一個一元一次方程,最後一行的解就是\(ans_n=r_{n+1}\)
之後的每一行\(r_i\):\(\sum\limits_{j=i}^n{ans_j\cdot r_{i,j}}=r_{i,n+1}\),移項就得到了解:\[ans_i=\dfrac{r_{i,n+1}-\sum\limits_{j=i+1}^n{ans_j\cdot r_{i,j}}}{r_{i,i}}=r_{i,n+1}-\sum\limits_{j=i+1}^n{ans_j\cdot r_{i,j}}\]

ans[n]=r[n][n+1];
    DRP(i,n-1,1)
    {
        ans[i]=r[i][n+1];
        RP(j,i+1,n)
            ans[i]-=r[i][j]*ans[j];
    }

1.4 一些特判細節和其他註意事項

如果方程的某一列元素均為\(0\),而常數項\(\neq 0\),那麽方程組是一定無解的。這裏直接特判就可以了。
另外,在交換行時可以特判一下兩行是否相等。減掉沒必要的操作可以稍微提速。

高斯消元算法