【C言語】行列の積の求め方

C言語での行列の積の求め方の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、C言語での「行列の積」の求め方について解説していきます。

C言語での行列の扱い方については下記ページで解説していますので、そもそもC言語で行列を扱う方法をご存知ない方は、事前に下記ページを読んでいただくことをオススメします。

C言語での行列の扱い方の解説ページアイキャッチ 【C言語】行列の扱い方

行列の積の演算の考え方

まずは、行列の積の求め方についておさらいしてきたいと思います。

下記の2行3列の行列 \(A\) と3行2列の行列 \(B\) の積 \(AB\) について考えていきましょう。

$$ A = \left ( \begin{array}{ccc} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23}  \end{array} \right ) $$

$$ B = \left ( \begin{array}{cc} b_{11} & b_{12} \\ b_{21} & b_{22} \\ b_{31} & b_{32} \end{array} \right ) $$

この積 \(AB\) の結果となる行列の行数は、行列 \(A\) の行数と一致します。さらに、積 \(AB\) の結果となる行列の列数は、行列 \(B\) の列数と一致します。つまり、積 \(AB\) の結果となる行列は2行2列となります。

そして、この積 \(AB\) の結果となる行列を下記の行列 \(C\) とした場合、

$$ C = \left ( \begin{array}{cc} c_{11} & c_{12} \\ c_{21} & c_{22} \end{array} \right ) $$

行列 \(C\) の各成分は下記のようにして求めることができます。

$$ \left ( \begin{array}{cc} c_{11} & c_{12} \\ c_{21} & c_{22} \end{array} \right ) = \left ( \begin{array}{cc} a_{11}b_{11} + a_{12}b_{21}  + a_{13}b_{31} & a_{11}b_{12} + a_{12}b_{22}  + a_{13}b_{32} \\ a_{21}b_{11} + a_{22}b_{21}  + a_{23}b_{31} & a_{21}b_{12} + a_{22}b_{22}  + a_{23}b_{32} \end{array} \right ) $$

つまり、行列 \(C\) の成分 \(c_{i j}\) は、行列 \(A\) における \(i\) 行目のみを抽出したベクトルと、行列 \(B\) における \(j\) 列目のみを抽出したベクトルとの内積ということになります。

$$ c_{i j} = \left ( \begin{array}{c} a_{i1} \\ a_{i2} \\ a_{i3} \end{array} \right ) \cdot \left ( \begin{array}{c} b_{1 j} \\ b_{2j} \\ b_{3j} \end{array} \right ) = a_{i1}b_{1j} + a_{i2}b_{2j}  + a_{i3}b_{3j} $$

内積とは、要は2つのベクトルの同じ位置の成分同士を掛け合わせ、掛け合わせた結果の全ての和を取ることを言います。

例えば \(c_{21}\) を求める際には、下記のように2つのベクトルの内積を求めれば良いことになります。

$$ c_{2 1} = \left ( \begin{array}{c} a_{21} \\ a_{22} \\ a_{23} \end{array} \right ) \cdot \left ( \begin{array}{c} b_{11} \\ b_{21} \\ b_{31} \end{array} \right ) = a_{21}b_{11} + a_{22}b_{21}  + a_{23}b_{31} $$

\(c_{21}\) を求めるためにやってることを図示すると下の図のようになります。

c21を求める際に行っている計算を図示したもの

まとめると、積 \(AB\) の結果となる行列 \(C\) における、\(i\) 行目・\(j\) 列目の成分、すなわち、\(c_{i j}\) を求める際には、下記のような処理を行なえば良いことになります。

  1. 行列 \(A\) における \(i\) 行目のみを抽出したベクトルを用意する
  2. 行列 \(B\) における \(j\) 列目のみを抽出したベクトルを用意する
  3. 1. と 2. で用意したベクトルの内積を求める

上記は最初にお見せした行列 \(A\) と行列 \(B\) にのみ有効な手順というわけではなく、下記を満たしていれば、あらゆる行数列数の行列の積に適用できる手順となります。

  • 行列 \(A\) の “列数” と行列 \(B\) の “行数” が一致する

例えば、行列 \(A\) が \(L\) 行 \(M_a\) 列の場合、1. で用意するベクトルの成分数は \(M_a\) となります。また、行列 \(B\) が \(M_b\) 行 \(N\) 列 の場合、2. で用意するベクトルの成分数は \(M_b\) となります。

ただし、内積では同じ位置の成分同士の掛け算を行なっていくわけですから、2つのベクトルで同じ成分数である必要があります。

つまり、積 \(AB\) を求める場合、行列 \(A\) の列数と行列 \(B\) の行数が一致する必要があります(上記の例で言うと、\(M_a = M_b\) が成立する必要がある)。

また、積 \(AB\) によって求まる行列の行数は “行列 \(A\) の行数” となり、列数は “行列 \(B\) の列数” となります。

行列の積の結果の1つの成分を求める

ここまでが、数学的な行列の積の考え方のおさらいとなります。

次は実際にプログラムとして「どうやって行列の積を実現すれば良いか」を考えていきましょう。

今回は、\(L\) 行 \(M\) 列の行列 \(A\) と \(M\) 行 \(N\) 列の行列 \(B\) の積 \(AB\) を求めることを考えていきたいと思います。

スポンサーリンク

必要な配列の変数宣言を行う

まず、積を行う対象となる2つの行列を用意する必要があり、下記のページで解説しているとおり、行列は2次元の配列で扱うことができます。

C言語での行列の扱い方の解説ページアイキャッチ 【C言語】行列の扱い方

今回は下記のように2次元配列 matAmatB を宣言し、matA を行列 \(A\) を扱う2次元配列、matB を行列 \(B\) を扱う2次元配列として使用していきたいと思います。

積演算を行う2次元配列の変数宣言
/* 2つの行列を用意 */
int matA[L][M];
int matB[M][N];

また、積 \(AB\) の結果となる行列を \(C\) とし、この行列 \(C\) を扱う2次元配列 matC の変数宣言も行っていきます。この matC のサイズは、行数が L、列数が N となるように変数宣言する必要があるので注意してください。これは積 \(AB\) の結果の行列が \(L\) 行 \(N\) 列となるためです。

積の結果となる2次元配列の変数宣言
/* matAとmatBの積の結果となる行列 */
int matC[L][N];

さらに、前述の通り、積の結果となる行列の各成分を求める際には、下記のような処理を行なっていくことになります。

  1. 行列 \(A\) における \(i\) 行目のみを抽出したベクトルを用意する
  2. 行列 \(B\) における \(j\) 列目のみを抽出したベクトルを用意する
  3. 1. と 2. で用意したベクトルの内積を求める

実は、後々不要にはなるのですが、まずは上記の求め方に則って処理を行うため、ベクトルを扱うための配列の宣言も行いたいと思います。

ベクトルは1次元配列で扱うことが可能であり、さらに内積計算時に用いるベクトルの成分数は \(M\) なので、下記のように M 個の要素を格納可能な2つの1次元配列 vecAvecB を宣言します。

1次元配列の変数宣言
/* 内積を求めるためのベクトル */
int vecA[M];
int vecB[M];

ベクトルを用意する

続いて行列の積を求めていきますが、まずは行列 \(C\) の1つの成分である matC[i][j] を求めることを考えていきたいと思います。

そのために、matA の i 行目の各成分を vecA に、matB の j 列目の各成分を vecB に格納していくことで、内積を求めるためのベクトルを用意していきます。

matA の列数および matB の行数はともに M ですので、下記のループ処理によりベクトルの用意をすることができます。

ベクトルの用意
/* 内積を求めるのに必要なベクトルを用意 */
for (k = 0; k < M; k++) {

    /* matAのi行目の成分だけのベクトルを用意 */
    vecA[k] = matA[i][k];

    /* matBのj列目の成分だけのベクトルを用意 */
    vecB[k] = matB[k][j];
}

matA は行を示す添字 i を固定し、さらに列を示す添字 k のみを変化させながら vecA に値を格納していっているため、vecA は matA の i 行目の成分のみを抽出したベクトルとなります。

vecAの準備の仕方の説明図

同様に、matB は列を示す添字 j を固定し、さらに行を示す添字 k のみを変化させながら vecB に値を格納していっているため、vecB は matB の j 列目の成分のみを抽出したベクトルとなります。

vecBの準備の仕方の説明図

ベクトルの内積を求める

続いて、vecAvecB の内積を求めていきます。この内積の結果が matC[i][j] となります。

この内積は、2つのベクトルの同じ位置の成分を掛け合わせ、その掛け合わせた結果を全て足し合わせることで求めることができます。

vecAvecB にはそれぞれ M 個の成分が存在するわけですから、下記の処理によって内積を求めることができます。

1つの成分を求める
/* vecAとvecBの内積を計算 */
inner_product = 0;
for (k = 0; k < M; k++) {

    /* 2つのベクトルの同じ位置の成分を
        掛け合わせたものを足し合わせていく */
    inner_product += vecA[k] * vecB[k];
}

/* 内積をmatCの成分として格納 */
matC[i][j] = inner_product;

以上の “ベクトルの用意” 〜 “内積の計算” の処理により、行列 \(C\) の1つの成分が matC[i][j] に求まったことになります。

スポンサーリンク

行列の積の結果の全ての成分を求める

あとは、行列 \(C\) の全成分に対して同様の処理を行なっていけば良いだけです。

matC は N 列の行列を表す2次元配列ですので、matC[i][j] を求める処理を、j0 から N -1 まで変化させながら繰り返し行えば、1行分の成分を求めることができることになります。

1行分の成分を求める
/* 1行分の成分を求める */
for (j = 0; j < N; j++) {

    /* ベクトルの用意とinner_productの計算 */

    /* 内積をmatCの成分として格納 */
    matC[i][j] = inner_product;
}

さらに、matC は L 行の行列を表す2次元配列ですので、上記の1行分の成分を求める処理を i0 から L -1 まで変化させながら繰り返し行えば、行列全体の成分を求めることができることになります。

行列全体の成分を求める
/* 行列全体の成分を求める */
for (i = 0; i < L; i++) {

    /* 1行分の成分を求める */
    for (j = 0; j < N; j++) {

        /* ベクトルの用意とinner_productの計算 */

        /* 内積をmatCの成分として格納 */
        matC[i][j] = inner_product;
    }
}

行列の積を求めるプログラムの例

ここまで解説してきた内容を1つのソースコードにまとめた「2つの行列の積」を求めるプログラムの例は下記のようになります。

積を求める
#include <stdio.h>

#define L 4 /* matAの行数 */
#define M 3 /* matAの列数・matBの行数 */
#define N 2 /* matBの列数 */

int main(void) {

    /* 2つの行列を用意 */
    int matA[L][M] = {
        { 1,  2,  3},
        { 4,  5,  6},
        { 7,  8,  9},
        {10, 11, 12}
    };
    int matB[M][N] = {
        {13, 14},
        {15, 16},
        {17, 18}
    };

    /* matAとmatBの積の結果となる行列 */
    int matC[L][N];

    /* 内積を求めるためのベクトルを用意 */
    int vecA[M];
    int vecB[M];

    /* 内積計算結果格納用 */
    int inner_product;

    int i, j ,k;

    /* 行列全体の成分を求める */
    for (i = 0; i < L; i++) {

        /* 1行分の成分を求める */
        for (j = 0; j < N; j++) {

            /* 内積を求めるのに必要なベクトルを用意 */
            for (k = 0; k < M; k++) {

                /* matAのi列目の成分だけのベクトルを用意 */
                vecA[k] = matA[i][k];

                /* matBのj列目の成分だけのベクトルを用意 */
                vecB[k] = matB[k][j];
            }

            /* 内積を計算 */
            inner_product = 0;
            for (k = 0; k < M; k++) {

                /* 2つのベクトルの同じ位置の成分を
                   掛け合わせたものを足し合わせていく */
                inner_product += vecA[k] * vecB[k];
            }

            /* 内積をmatCの成分として格納 */
            matC[i][j] = inner_product;
        }
    }

    /* matAとmatBの積を表示 */
    for (i = 0; i < L; i++) {
        for (j = 0; j < N; j++) {
            printf("%d,", matC[i][j]);
        }
        printf("\n");
    }

    return 0;
}

上記のソースコードのプログラムを実行すれば、次のように2つの行列の積の結果が表示されます。

94,100,
229,244,
364,388,
499,532,

行列の行数と列数は下記で定義しており、ここを変更すれば、様々な行数・列数の行数の積を求めることができます。

行数と列数の定義
#define L 4 /* matAの行数 */
#define M 3 /* matAの列数・matBの行数 */
#define N 2 /* matBの列数 */

ただし、行列の行数や列数を変更した場合、積を求める前に matAmatB に格納しておく必要のある成分数も変わるので注意してください。

行列として扱う2次元配列への値の格納に関しては下記ページで解説していますので、詳しくは下記ページを参照していただければと思います。

C言語での行列の扱い方の解説ページアイキャッチ 【C言語】行列の扱い方

行列の積を求めるプログラムの例(改良)

前述で紹介したソースコードのプログラムでも行列の積を求めることはできるのですが、ちょっと無駄な処理があるので改良していきたいと思います。

下記の3つの※マークの部分に注目すれば、結局内積計算時に用いる vecA[k]matA[i][k] であり、vecB[k]matB[k][j] であることが分かります。

vecAとmatA・vecBとmatBの関係
/* 内積を求めるのに必要なベクトルを用意 */
for (k = 0; k < M; k++) {

    /* matAのi列目の成分だけのベクトルを用意 */
    vecA[k] = matA[i][k]; /* ※ */

    /* matBのj列目の成分だけのベクトルを用意 */
    vecB[k] = matB[k][j]; /* ※ */
}

/* 内積を計算 */
inner_product = 0;
for (k = 0; k < M; k++) {

    /* 2つのベクトルの同じ位置の成分を
       掛け合わせたものを足し合わせていく */
    inner_product += vecA[k] * vecB[k]; /* ※ */
}

ですので、わざわざ vecA[k]vecB[k]matA[i][k]matB[k][j] を格納しなくても、直接 matA[i][k]matB[k][j] から内積を求めることが可能です。

で、この方が、内積を求める際に毎回 vecAvecB を用意する必要がなくなるので処理効率が上がります。

vecAvecB の用意を省いたソースコードが下記となります。結果は先ほど紹介したものと変わらないため、結果の紹介は省略させていただきます。

積を求める(改良)
#include <stdio.h>

#define L 4 /* matAの行数 */
#define M 3 /* matAの列数・matBの行数 */
#define N 2 /* matBの列数 */

int main(void) {

    /* 2つの行列を用意 */
    int matA[L][M] = {
        { 1,  2,  3},
        { 4,  5,  6},
        { 7,  8,  9},
        {10, 11, 12}
    };
    int matB[M][N] = {
        {13, 14},
        {15, 16},
        {17, 18}
    };

    /* matAとmatBの積の結果となる行列 */
    int matC[L][N];

    /* 内積計算結果格納用 */
    int inner_product;

    int i, j ,k;

    /* 行列全体の成分を求める */
    for (i = 0; i < L; i++) {

        /* 1行分の成分を求める */
        for (j = 0; j < N; j++) {

            /* matAのi行目とmatBのj列目の内積を計算 */
            inner_product = 0;
            for (k = 0; k < M; k++) {

                /* 2つのベクトルの同じ位置の成分を
                   掛け合わせたものを足し合わせていく */
                inner_product += matA[i][k] * matB[k][j];
            }

            /* 内積をmatCの成分として格納 */
            matC[i][j] = inner_product;
        }
    }

    /* matAとmatBの積を表示 */
    for (i = 0; i < L; i++) {
        for (j = 0; j < N; j++) {
            printf("%d,", matC[i][j]);
        }
        printf("\n");
    }

    return 0;
}

ちょっと回りくどい解説に感じたかもしれませんが、割と行列の積のプログラムはバグりやすいです。

ただ、今回の解説のように段階を踏みながらプログラミングすることでバグを防ぎやすくなりますし、頭の中を整理しながらプログラミングすることもできると思います。

スポンサーリンク

まとめ

このページでは、C言語での「行列の積」の求め方について解説しました!

行列 \(A\) と行列 \(B\) の積 \(AB\) を求める際には、行列 \(A\) から1行分の成分のみを抽出したベクトルと行列 \(B\) から1列分の成分のみを抽出したベクトルを用意し、その2つのベクトルの内積を求めることで積 \(AB\) の1つの成分を求めることができます。

そして、これを積 \(AB\) の全成分に対して繰り返すことで、行列 \(A\) と行列 \(B\) の積 \(AB\) の全成分を求めることができます。

3重ループになるので難しく思えますが、「ベクトルの内積を求めるための1重ループ処理」を全成分に対して繰り返すために2重ループさせると考えると、ちょっと問題を簡略化して考えることができるかなぁと思います!

同じカテゴリのページ一覧を表示