【算法】遞歸算法之n階矩陣行列式求解

最近高等代數正好講到這裏,此篇文章正好對所學知識作一個具體程序實踐。程序員

設計算法時使用遞歸的思想是一個程序員的基本素質,遞歸能夠把一個很龐大的問題轉化爲規模縮小了的同類問題的子問題,經過這一思想,咱們編程時運用遞歸可使用不多的代碼來處理很大的問題。這篇文章將會講到遞歸算法的運用。算法

在數學中,不少數學函數都是由一個公式來表達的,好比 f(x) = 100x; 這個公式可讓咱們把長度米轉換成長度釐米。 有了這個公式,在程序中敲出一行代碼,寫出一個函數(function)來計算實在是太簡單方便了,就像這樣。編程

int convert(int m){
    return 100*m;
}

咱們就寫好了一個函數來進行米到釐米的單位換算。數組

可是有的時候,數學函數會以不太標準的形式來定義,好比這個函數,他知足 f(0)=0並且 f(x) = 2f(x-1)+x; 從這個函數定義咱們能夠得出 f(1)=1;f(2)=3;等等。當一個函數用他本身來定義時就稱爲這個函數是遞歸的。數據結構

通俗地講,就是從前有個山,山裏有個廟,廟裏有個老和尚再給小和尚講故事,講的是:從前有個山,山裏有個廟,廟裏有個老和尚再給小和尚講故事。。。。這就是遞歸。函數

好了說了這麼多大家確定仍是一頭霧水,如今來實踐一下。this

遞歸求階乘

剛開始學編程的同窗必定會寫求階乘的函數,使用for循環或者while循環均可以,可是遞歸卻徹底用不上這兩個循環。spa

public static int factorial(int a){
 
    if (a==0 || a==1){
        return 1;
    }
 
    return a*factorial(a-1);
}

上面的代碼就是遞歸求階乘的方法,a是須要傳入的參數,好比咱們要求5的階乘就傳入5這樣factorial函數最終的返回值爲120;設計

分析這段代碼,他的第3行到第五行處理了 基準狀況(Base Case),在這個狀況下,函數的值能夠直接算出而不用求出遞歸。就像上文提到的函數f(x) = 2f(x-1)+x;若是沒有f(0)=0這個事實在數學上沒有意義同樣。 再編程中,若是沒有基準狀況也是無心義的。第7行執行的是遞歸調用。code

因此所,設計遞歸算法,須要包含如下兩個基本法則:

一、基準情形(Base Case),必須總要有某些基準的清醒,在這個情形中,不執行遞歸就能求解。

二、不斷推動(Making Progress),對於須要遞歸求解的情形,遞歸調用必須總可以朝着一個基準情形推動。這樣的話,不斷推動到基準情形,到達基準情形的時候遞歸纔會推出,獲得返回值。

n階矩陣行列式的求解

有了剛在知識的鋪墊,如今咱們能夠動手寫一個程序來用遞歸計算n階矩陣的行列式了。

首先來看下二階矩陣的求法

也就是說2×2矩陣的元素交叉相乘再想減便可求出行列式。

接下來是3階矩陣

3×3矩陣求解中,選擇任意行或者列,在那一行/列中,移除那個元素所在的行和列好比選擇a11,則移除第一行和第一列,這樣矩陣就變成了2×2的,再按照剛纔的方法求2×2矩陣的行列式便可。以後整個行或列的3個元素進行此類運算後相加就是3×3的行列式。

n x n矩陣

n階矩陣就和3階矩陣求解的方法同樣了,使用3×3求解的方法,好比4階矩陣,將4階消除成3階,而後再變成2階來算。可是矩陣每上升一個維度,計算量就會擴大不少。

知道了n階矩陣行列式的計算思路後,就能夠開始編寫算法了。

首先是數據結構設計,咱們須要設計一個矩陣類來提供便利,這個類有兩個成員,一個二維數組,用來儲存矩陣,一個整數,來儲存矩陣的行數或列數(行列式必須是方矩陣才能夠求解因此行列無所謂)。

如下是整個Matrix類的設計:

static class Matrix{
 
    private int rowNum=0;
 
    private int[][] array;
 
    //Constructor
    public Matrix(int rowNum){
        this.rowNum = rowNum;
        array = new int[rowNum][rowNum];
    }
 
    public Matrix(int[][] array){
        this.array = array;
        this.rowNum = array.length;
    }
 
    int counter = 0; //For add Element
 
    public void addElement(int a){
        array[(counter)/rowNum][counter%rowNum] = a;
        counter++;
    }
 
    //Print the instance itself
    public void printMat(){
        for (int i=0;i < rowNum ;i++){
            for (int j=0;j < rowNum ;j++){
                 System.out.print(array[i][j]+\t;);
            }
            System.out.println();
        }
    }
 
    //Setter and Getter
    public int getRowNum() {
        return rowNum;
    }
 
    public int[][] getArray() {
        return array;
    }
 
    public void setArray(int[][] array) {
        this.array = array;
    }
 
}

Matrix類中有兩個構造方法:傳入整數a會初始化一個axa大小的空矩陣,傳入一個二維數組的話便可根據二維初始化一個Matrix對象。

Matrix類中有一個方法比較特殊:addElement方法,經過不斷調用這個函數便可向一個Matrix實例進行有順序的負值,第一次調用則會更改第第一行第一列位置上的值,第二次調用則會更改第一行第二列上的值,以此類推。

接下來就是設計一個MatrixCalculator類的,這個類中的一個成員方法能夠求出行列式,我命名他爲getDet(); 在計算行列式的時候須要移除元素所在的行和列,生成一個減少了一個維度的矩陣,咱們須要編寫一個方法來完成這個操做,我命名他爲removeRowAndCol();還有一個方法,因爲相加的時候會產生符號的改變,因此須要寫一個方法來計算矩陣中一個元素的cofactor,命名爲getCofactor。

如下就是removeRowAndCol方法的代碼:傳入須要移除的行和列和一個Matrix對象,函數會返回消除了指定行和列的Matrix對象。

public static Matrix removeRowAndCol(int row,int col,Matrix mat){
 
    int matRowNum = mat.getRowNum();
    int[][] arr = mat.getArray();
 
    Matrix matrix = new Matrix(matRowNum-1);
 
    for (int i = 0;i < matRowNum; i++){
        for (int j = 0 < matRowNum; j++){
                if (i!=row && j!=col) {
                    matrix.addElement(arr[i][j]);
                }
        }
    }
 
    matrix.printMat();
    return matrix;
 
}

如下是getCofactor方法:因爲個人算法只會去遍歷矩陣第一列來進行求解,因此獲得Cofactor的代碼就變得簡單不少。

public static int getCofactor(int colNum){
    if (colNum%2 == 0){
        return 1;
    }else {
        return -1;
    }
}

接下來就是核心的遞歸求解行列式的算法了,先理一下思路,遞歸算法的兩個要素:基準情形,不斷推動

對於n階矩陣什麼是基準情形呢?就是矩陣被降爲2×2維度的時候,直接返回交叉相乘的差便可,不斷推動,若是是一個4階矩陣,算法會先把4將爲3×3矩陣,而後3×3再拆成3個2×2矩陣來達到基準情形來算出答案,就和咱們手算行列式時用到的方法同樣,手算時候也遵循這一算法。

public static int getDet(Matrix targetMatrix){
    //Base (Finally reduced to 2 x 2 Matrix)
    if (targetMatrix.rowNum == 2){
        int[][] arr = targetMatrix.getArray();
        // a*d - b*c
        return arr[0][0]*arr[1][1] - arr[0][1]*arr[1][0];
    }
 
    //MARK- Recursion: to reduce dimension
    int det = 0;
    int colNum = targetMatrix.rowNum;
    for (int i = 0; < colNum;i++){
        det+= (targetMatrix.getArray()[i][0])*getCofactor(i)*getDet(removeRowAndCol(i,0,targetMatrix));
    }
 
    return det;
}

只有不到20行代碼,可是卻能夠解決nxn的矩陣,是否是很神奇,這就是遞歸的優點,把一個很龐大的問題轉化爲規模縮小了的同類問題的子問題來求解。n階矩陣最後被降爲若干個2×2矩陣。

加上適當的輸出語句,來求解一個3階矩陣行列式試一下:

2   1    2 
-1   5   21 
13 -1  -17

Element: 2
Cofactor: 1
Removing Row 0 Col 0
A 2 x 2 Matrix is initialized 
5 21 
-1 -17 
END State Reached
Det: -64

Element: -1
Cofactor: -1
Removing Row 1 Col 0
A 2 x 2 Matrix is initialized 
1 2 
-1 -17 
END State Reached
Det: -15

Element: 13
Cofactor: 1
Removing Row 2 Col 0
A 2 x 2 Matrix is initialized 
1 2 
5 21 
END State Reached
Det: 11
Result: 0

可是有一個問題須要注意,就是這個算法的複雜度。

算法複雜度

這個算法的複雜度爲O(n!), 這意味着他的運行速度很慢,隨着問題規模的增加,時間會大幅度增加。在個人電腦上,計算3×3到7×7內規模的矩陣,電腦均可以秒算出來,可是若是是一個10×10的矩陣,電腦須要54秒鐘,到了11×11時間將會變得更長,下圖是這個算法隨着問題規模增加對運行時產生影響的曲線。

能夠看出7×7矩陣須要遞歸517次,到了10×10須要大約260萬次遞歸運算才能獲得結果。可見問題規模增加後時間的開銷是十分巨大的。

原文:http://miketech.it/recursion_algorithm/

相關文章
相關標籤/搜索