矩陣除了基本的加法和乘法以外,還有很多種運算或分解。 其中,有不少運算都會用到 row 的基本操作(colunm 可自行類推), 例如兩個 row 交換,某個 row 乘上一個常數加到另外一個 row 等等, 因此我們先來示範這兩種操作。 以下範例,會亂數產生一個矩陣,並進行上述的基本操作:
#include <stdio.h> #include <time.h> #include <stdlib.h> #define SIZE 5 void printArray(double array[][SIZE], int rowNum, int colNum){ int i, j; for(i=0;i<rowNum;i++){ for(j=0;j<colNum;j++){ printf("%.4f ", array[i][j]); } printf("\n"); } printf("\n"); } void rowExchange(double array[][SIZE], int size, int idx1, int idx2){ int i; double temp; for(i=0;i<size;i++){ temp = array[idx1][i]; array[idx1][i] = array[idx2][i]; array[idx2][i] = temp; } } void addRowToAnother(double array[][SIZE], int size, double c, int idx1, int idx2){ int i; for(i=0;i<size;i++){ array[idx2][i] += c * array[idx1][i]; } } int main() { double a[SIZE][SIZE]; int i, j, k; srand(time(NULL)); for(i=0;i<SIZE;i++){ for(j=0;j<SIZE;j++){ a[i][j] = rand()%10; } } printArray(a, SIZE, SIZE); printf("Swap row 1,4:\n"); rowExchange(a, SIZE, 1, 4); printArray(a, SIZE, SIZE); printf("2 * row 3 + row 0:\n"); addRowToAnother(a, SIZE, 2, 3, 0); printArray(a, SIZE, SIZE); return 0; }接著,我們來示範如何以高斯消去法(Gaussian Elimination)將矩陣進行三角化。 這個方法,我們其實在國中就學過,那時稱之為"加減消去法"; 高中時則告訴你"高斯消去法"這個名稱,以及引入矩陣的概念。 而也許你記得為了人工計算方便,我們會以好計算的數字所在的 row 當基準,來消去其他的 row; 在電腦裡面我們一樣可以如此操作,但還有另外一個理由,就是為了數值計算的穩定度。 以下範例,會示範如何將矩陣進行三角化,並展示中間每一步的運算過程:
#include <stdio.h> #include <time.h> #include <stdlib.h> #include <math.h> #define SIZE 5 void printArray(double array[][SIZE], int rowNum, int colNum){ int i, j; for(i=0;i<rowNum;i++){ for(j=0;j<colNum;j++){ printf("%7.4f ", array[i][j]); } printf("\n"); } printf("\n"); } void rowExchange(double array[][SIZE], int size, int idx1, int idx2){ int i; double temp; for(i=0;i<size;i++){ temp = array[idx1][i]; array[idx1][i] = array[idx2][i]; array[idx2][i] = temp; } } void addRowToAnother(double array[][SIZE], int size, double c, int idx1, int idx2){ int i; for(i=0;i<size;i++){ array[idx2][i] += c * array[idx1][i]; } } int main() { double a[SIZE][SIZE], maxVal; int i, j, maxRowIdx; srand(time(NULL)); for(i=0;i<SIZE;i++){ for(j=0;j<SIZE;j++){ a[i][j] = rand()%10; } } printf("Original matrix:\n"); printArray(a, SIZE, SIZE); for(j=0;j<SIZE-1;j++){ /* 在目前要處理的 column j 當中,找尋絕對值最大的 row i */ maxVal = fabs(a[j][j]); maxRowIdx = j; /* 先設對角線上是最大,再去比其他的 */ for(i=j+1;i<SIZE;i++){ if(fabs(a[i][j])>maxVal){ maxVal = fabs(a[i][j]); maxRowIdx = i; } } if(maxVal==0){ continue; } /* 如果最大值不在對角線上,則進行 row 的交換 */ if(maxRowIdx!=j){ printf("Swap row %d and %d:\n", j, maxRowIdx); rowExchange(a, SIZE, j, maxRowIdx); printArray(a, SIZE, SIZE); } /* 消去法 */ for(i=j+1;i<SIZE;i++){ printf("%.4f * row %d + row %d:\n", -a[i][j]/a[j][j], j, i); addRowToAnother(a, SIZE, -a[i][j]/a[j][j], j, i); printArray(a, SIZE, SIZE); } } return 0; }上述範例中的三角化,是把矩陣變成上三角矩陣(upper triangular matrix), 當然如果你改變一下迴圈的方向, 很容易就可以得到下三角矩陣(lower triangular matrix),甚至是單位矩陣(identity matrix)。上面提到可以透過列運算,把矩陣消去為單位矩陣,也許會讓你想起高中時的反矩陣求法: 先把原本矩陣跟單位矩陣,橫向並列為增廣矩陣; 而把左半邊原本矩陣透過列運算變成單位矩陣時,右半邊的矩陣就是反矩陣。 以下範例將示範基於此方法的反矩陣計算:
#include <stdio.h> #include <time.h> #include <stdlib.h> #include <math.h> #define SIZE 5 void printArray(double array[][SIZE], int rowNum, int colNum){ int i, j; for(i=0;i<rowNum;i++){ for(j=0;j<colNum;j++){ printf("%7.4f ", array[i][j]); } printf("\n"); } printf("\n"); } void rowExchange(double array[][SIZE], int size, int idx1, int idx2){ int i; double temp; for(i=0;i<size;i++){ temp = array[idx1][i]; array[idx1][i] = array[idx2][i]; array[idx2][i] = temp; } } void addRowToAnother(double array[][SIZE], int size, double c, int idx1, int idx2){ int i; for(i=0;i<size;i++){ array[idx2][i] += c * array[idx1][i]; } } void inv(double a[][SIZE], double out[][SIZE], double n){ int i, j, maxRowIdx; double maxVal, c; /* Downward */ for(j=0;j<SIZE-1;j++){ /* 在目前要處理的 column j 當中,找尋絕對值最大的 row i */ maxVal = fabs(a[j][j]); maxRowIdx = j; /* 先設對角線上是最大,再去比其他的 */ for(i=j+1;i<SIZE;i++){ if(fabs(a[i][j])>maxVal){ maxVal = fabs(a[i][j]); maxRowIdx = i; } } if(maxVal==0){ continue; } /* 如果最大值不在對角線上,則進行 row 的交換 */ if(maxRowIdx!=j){ rowExchange(a, SIZE, j, maxRowIdx); rowExchange(out, SIZE, j, maxRowIdx); } /* 消去法 */ for(i=j+1;i<SIZE;i++){ c = -a[i][j]/a[j][j]; addRowToAnother(a, SIZE, c, j, i); addRowToAnother(out, SIZE, c, j, i); } } /*Upward */ for(j=SIZE-1;j>0;j--){ /* 消去法 */ for(i=j-1;i>=0;i--){ c = -a[i][j]/a[j][j]; addRowToAnother(a, SIZE, c, j, i); addRowToAnother(out, SIZE, c, j, i); } } /* Diagonal */ for(i=0;i<SIZE;i++){ for(j=0;j<SIZE;j++){ out[i][j] /= a[i][i]; } } } int main() { double a[SIZE][SIZE], aInv[SIZE][SIZE]; int i, j; srand(time(NULL)); for(i=0;i<SIZE;i++){ for(j=0;j<SIZE;j++){ a[i][j] = rand()%10; aInv[i][j] = (i==j)?1:0; } } printf("Original matrix:\n"); printArray(a, SIZE, SIZE); printArray(aInv, SIZE, SIZE); inv(a, aInv, SIZE); printf("Inversed matrix:\n"); printArray(a, SIZE, SIZE); printArray(aInv, SIZE, SIZE); return 0; }關於行列式(determinant)的計算,也許你在線性代數等課會學到可以先進行三角化再來求解, 不過我們還是先來看看高中教的降階展開,要怎樣寫成程式。 在降階的方法中,要求 n-by-n 矩陣的行列式,需要計算 n 個 (n-1)-by-(n-1) 矩陣的行列式, 因此需要使用遞迴。範例如下:
#include <stdio.h> #include <time.h> #include <stdlib.h> #include <math.h> #define SIZE 5 void printArray(double array[][SIZE], int rowNum, int colNum){ int i, j; for(i=0;i<rowNum;i++){ for(j=0;j<colNum;j++){ printf("%7.4f ", array[i][j]); } printf("\n"); } printf("\n"); } double det(double array[][SIZE], int n){ double temp[SIZE][SIZE], result = 0; int sign = 1, i, ii, jj, iCounter; if(n==2){ return array[0][0] * array[1][1] - array[0][1] * array[1][0]; } for(i=0;i<n;i++){ /* copy */ iCounter = 0; for(ii=0;ii<n;ii++){ if(ii==i){ continue; } for(jj=1;jj<n;jj++){ temp[iCounter][jj-1] = array[ii][jj]; } iCounter++; } result += sign * array[i][0] * det(temp, n-1); sign = -sign; } return result; } int main() { double a[SIZE][SIZE]; int i, j; srand(time(NULL)); for(i=0;i<SIZE;i++){ for(j=0;j<SIZE;j++){ a[i][j] = rand()%10; } } printf("Original matrix:\n"); printArray(a, SIZE, SIZE); printf("Determinant: %.4f\n", det(a, SIZE)); return 0; }如果你在自己電腦上執行這個範例,可能會發現當矩陣大小增加到某個值後,程式會跑到天荒地老還沒有結果出現。 這是因為,若矩陣大小為 n-by-n,則降階法的執行時間,大約會與 nn 成比例(正式的說法,是"時間複雜度為O(nn)")。 因此,求行列式時,還是要透過三角化,會比較有效率。