矩陣加法與乘法

 

  實 習 內 容






1. 檔案讀取與寫入

2. 二維陣列定義與運用

3. 二維陣列如何作為函數的參數

1

下載下列資料檔案及程式檔案到同一個資料匣中執行:
資料檔案

範例執行檔案

這個程式由檔案中讀取兩個矩陣的資料, 計算矩陣的和以及乘積, 是一個標準的資料處理程式,可以用如下的 top-down 分析方法來設計這個程式。

首先, 由 main() 函數開始設計, 將整個程式的功能切割為幾個主要的處理程序:

  1. 定義存放矩陣資料的陣列、檔案指標變數
  2. 檔案的開啟
  3. 矩陣資料的讀取
  4. 檔案的關閉
  5. 矩陣相加
  6. 矩陣相乘

也可以把上面的設計直接用註解放在 main() 函數中

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    /* 定義存放矩陣資料的陣列、檔案指標變數 */

    /* 檔案的開啟 */

    /* 矩陣資料的讀取 */

    /* 檔案的關閉 */

    /* 矩陣相加 */

    /* 矩陣相乘 */

    system("pause");
    return 0;
}

下面步驟中, 在設計每一個處理程序之前, 把它的輸入資料先設計好, 運用適當型態的變數/陣列存放起來裡, 把它的輸出資料的格式先設計好, 然後運用選擇/重複結構設計處理程序的內容, 將輸入資料正確地轉換為輸出資料

2

資料檔案 裡面包含下列格式的資料:

4 7  matrix 1
1 2 3 4 5 6 7
3 1 3 1 3 1 3
2 1 0 -1 -2 -3 5
2 4 5 1 9 3 6
4 7  matrix 2
1 2 1 2 1 2 3
-2 -1 0 -2 -1 0 5
3 -2 1 0 1 -2 3
7 6 5 4 3 2 1
第一列包含矩陣的列數與行數, 還有一段文字註解 "matrix 1" 或是 "matrix 2", 這一段註解程式需要跳過, 不需要記在變數中 。接下來每一列就是矩陣中的一列; 第二個矩陣就接續在第一個矩陣之後, 也是先包含矩陣的列數與行數, 註解文字, 含後是一列一列的資料。

3

變數定義: 接下來我們在 main(): 函數中定義存放矩陣的二維陣列, 以及存放矩陣維度的 number_of_rows 及 number_of_columns 變數如下:

#define ROWS 10
#define COLS 10

int main(void)
{
    int matrix1[ROWS][COLS], matrix2[ROWS][COLS];
    int nrow1, ncol1, nrow2, ncol2;
    ...
}

4

以文字模式開啟及關閉資料檔案:

int main(void)
{
    ....
    FILE *fp;
    ....
    fp = fopen("matrix.dat", "r");
    if (fp == 0)
    {
        printf("Error in opening file matrix.dat\n");
        exit(1);
    }
    ....
    /* 處理資料 */
    ....
    fclose(fp);
}

5

由開啟的檔案中讀取矩陣資料:

首先將矩陣有幾列, 有幾行讀入程式, 然後運用 fgets() 將這一列剩餘的註解字串由檔案中讀出到 buf 字元陣列中, 以便繼續讀取矩陣的資料內容:

void readFile(FILE *fp,
              int *nrow1, int *ncol1, int matrix1[][COLS],
              int *nrow2, int *ncol2, int matrix2[][COLS])
{
    char buf[100];
    int i, j;


    /* the first matrix */

    /* reading number of rows and number of columns */

    fscanf(fp, "%d%d", nrow1, ncol1);

    if ((*nrow1 > ROWS) || (*ncol1 > COLS))
    {
        printf("Matrix dimension is too large!\n");
        exit(1);
    }

    /* skipping all the remaining messages till the end of line */
    fgets(buf, 100, fp);

    for (i=0; i<*nrow1; i++)
        for (j=0; j<*ncol1; j++)
            fscanf(fp, "%d", &matrix1[i][j]);

    /* the second matrix */
    fscanf(fp, "%d%d", nrow2, ncol2);

    if ((*nrow2 > ROWS) || (*ncol2 > COLS))
    {
        printf("Matrix dimension is too large!\n");
        exit(1);
    }

    fgets(buf, 100, fp);
    for (i=0; i<*nrow2; i++)
        for (j=0; j<*ncol2; j++)
            fscanf(fp, "%d", &matrix2[i][j]);
}
然後在 main() 函數中加入 readFile 函數的呼叫如下:
void readFile(FILE *fp,
              int *nrow1, int *ncol1, int matrix1[][COLS],
              int *nrow2, int *ncol2, int matrix2[][COLS]);
...
int main(void)
{
    ....  
    /* 檔案的開啟 *
    /* 矩陣資料的讀取 */
    readFile(fp, &nrow1, &ncol1, matrix1, &nrow2, &ncol2, matrix2);

    /* 檔案的關閉 */
    /* 矩陣相加 */
    /* 矩陣相乘 */

}

6

矩陣內容的列印:

C 的標準輸出入函數庫裡面沒有自動列印陣列的工具, 所以你需要寫一個工具函數來做列印的動作, 在計算的過程裡你就可以隨時把整個矩陣的資料印出來檢查; 原先我們在設計的時候沒有談到要列印矩陣, 不過我們寫的任何一段程式碼裡面其實都很容易發生錯誤, 你需要準備好可以隨時檢查錯誤的工具。

撰寫一小段程式把矩陣的內容印在螢幕上, 這個函式的原型如下:

    void showMatrix(char message[], int nrow, int ncol, 
                    int matrix[][COLS]);

第一個陣列參數是設計來傳送一段說明文字, 可以在列印矩陣資料之前先列印這些說明出來, 否則螢幕上突然出現一堆數字, 很難知道它們代表什麼意義:

void showMatrix(char message[], int nrow, int ncol, int matrix[][COLS])
{
    int i, j;
    printf("%s ====== rows=%d cols=%d ======\n", message, nrow, ncol);
    for (i=0; i<nrow; i++)
    {
        for (j=0; j<ncol; j++)
            printf("%5d", matrix[i][j]);
        printf("\n");
    }
}
你可以用類似下面的呼叫來使用上面的工具函數
int main(void)
{
    int matrix1[ROWS][COLS];
    int nrows, ncols;
    ....
    readFile(...);
    showMatrix("matrix 1 = ", nrows, ncols, matrix1);
}

7

矩陣加法:

除了很直覺地用兩層的迴圈把矩陣加起來, 你還需要先檢查一下兩個矩陣的大小, 判斷能不能相加, 如果原本的矩陣不再需要了, 你也可以把加起來的結果直接放到其中一個矩陣裡面, 範例程式碼如下:

void matrixAddition(int nrow1, int ncol1, int matrix1[][COLS],
                    int nrow2, int ncol2, int matrix2[][COLS])
{
    int i, j, matrixResult[ROWS][COLS];
    if ((nrow1 != nrow2) || (ncol1 != ncol2))
    {
        printf("These two matrices are of different sizes, cannot be added!!\n");
        return;
    }
    for (i=0; i<nrow1; i++)
        for (j=0; j<ncol1; j++)
            matrixResult[i][j] = matrix1[i][j] + matrix2[i][j];

    showMatrix("Matrix1 + Matrix2 equals ", nrow1, ncol1, matrixResult);
}

8

矩陣乘法:

首先, 應該先檢查兩個矩陣的維度是不是可以乘起來。

其次, 你可以運用三層的迴圈來計算矩陣的乘法, 在計算的過程中, 你需要定義一個新的矩陣來存放結果, 不能直接覆蓋掉原來的矩陣, 因為不管是 matrix1 或是 matrix2 的內容都在整個乘法過程中用到, 如果在過程中覆蓋掉了, 相乘的結果就不對了:

for (i=0; i<nrow1; i++)
    for (j=0; j<nrow2; j++)
    {
        matrixResult[i][j] = 0;
        for (k=0; k<ncol1; k++)
            matrixResult[i][j] += matrix1[i][k] * matrix2[j][k];
    }
這段程式碼容易閱讀嗎? 陣列的註標使用 i, j, k 基本上還符合平常的數學習慣, 不過 matrix[i][k] * matrix[j][k] 比較怪一點, 平常我們都寫 Aik*Bkj 的, 事實上如果我們先轉置矩陣, 然後再做乘法, 寫出來的程式就會更直覺一些, 更符合平常的數學習慣, 雖然這樣子做好像使用一些多餘的程式碼, 花費一些不需要的計算時間, 使得程式看起來比較笨拙, 但是這是有好處的, 可以使你的程式碼和實際運作的數學模型相符合, 以後在閱讀和修改程式的時候不需要去傷腦筋思考這些註標代表的意義, 這兩個優點事實上比起你在 1TB 的硬碟上省下 100 個位元組的空間, 在每秒完成 109個運算的機器上省下1000個運算重要得多。範例程式如下:
void matrixMultiplication(int nrow1, int ncol1, int matrix1[][COLS],
                          int nrow2, int ncol2, int matrix2[][COLS])
{
    int i, j, k, matrix2t[ROWS][COLS], matrixResult[ROWS][COLS];
    if ((ncol1 != ncol2))
    {
        printf("The number of columns of the first matrix and "
               "the number of rows of the transposed second matrix are "
               " different, cannot be multiplied!!\n");
        return;
    }

    /* transposition of matrix2 */
    for (i=0; i<nrow2; i++)
        for (j=0; j<ncol2; j++)
            matrix2t[j][i] = matrix2[i][j];

    for (i=0; i<nrow1; i++)
        for (j=0; j<nrow2; j++)
        {
            matrixResult[i][j] = 0;
            for (k=0; k<ncol1; k++)
                matrixResult[i][j] += 
                    matrix1[i][k] * matrix2t[k][j];
        }

    showMatrix("Matrix1 is ", nrow1, ncol1, matrix1);
    showMatrix("Matrix2' is ", ncol2, nrow2, matrix2t); 
    showMatrix("Matrix1 * Matrix2' equals ", nrow1, nrow2, matrixResult);
}

9

接下來你可以嘗試寫一個應用程式運用高斯消去法來計算一個方陣的反矩陣? 或是嘗試寫一個程式來計算矩陣的行列式值? 或是嘗試寫一個程式運用 Gram-Schmidt 方法來求出空間的正交基底?

計算機程式設計實習 首頁

製作日期: 101/11/16 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@ntu.edu.tw