C言語で学ぶ!行列操作の全てを解説する7つのステップ – Japanシーモア

C言語で学ぶ!行列操作の全てを解説する7つのステップ

C言語を使った行列操作のイメージ図C言語
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

このサービスは複数のSSPによる協力の下、運営されています。

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

行列操作はプログラミングの中で重要な要素の一つです。

C言語を使って学ぶ行列操作は初心者にとって特に理解が深まるものとなるでしょう。

この記事では、C言語で行列操作を行うための全てを分かりやすく解説します。

理論から具体的なコードまで、初心者でも安心して理解できる内容となっています。

●C言語とは

C言語は1972年にデニス・リッチーによって開発された汎用プログラミング言語です。

そのシンプルで強力な機能により、さまざまなアプリケーションやオペレーティングシステムの開発に使われてきました。

C言語は直感的な文法と効率の良い記述性を持ち、初心者にとって学びやすい言語とされています。

●行列とは

行列は、数値や記号、式などを格子状に並べたものを指します。

行列の一つ一つの要素は、特定の位置情報(行と列)によって参照されます。

○行列の基本的な理論

行列には加算、減算、乗算、転置、逆行列など、多くの操作が存在します。

これらの操作は数学的な問題解決やデータ解析など、さまざまな応用があります。

○行列の重要性

行列は、複数の数値データを効率的に扱うためのツールとして利用されます。

例えば、システムの状態を一度に表現したり、大量のデータをまとめて計算したりする場合などに行列は非常に有効です。

●C言語で行列を表現する

行列はC言語で二次元配列として表現することができます。

○行列の表現方法

行列の表現方法について解説します。

3×3の行列を表現するコードを紹介します。

#include <stdio.h>

int main(){
    int matrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    return 0;
}

このコードでは、3×3の行列を二次元配列として定義しています。

matrixは行列名で、[3][3]はそれぞれ行と列の数を表しています。

配列の中には行列の要素が順に格納されています。

○行列の初期化

行列の初期化について解説します。

下記のコードでは、全ての要素を0で初期化する行列を作成しています。

#include <stdio.h>

int main(){
    int matrix[3][3] = {0};
    return 0;
}

このコードでは、全ての要素を0で初期化する行列を作成しています。

{0}を使用することで、全ての要素が0で初期化されます。

○行列の表示

行列の表示について解説します。

下記のコードは、先ほど定義した3×3の行列を表示するものです。

#include <stdio.h>

int main(){
    int i, j;
    int matrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    return 0;
}

このコードでは、printf関数を使って行列の各要素を出力しています。

また、forループを2つ使用して行列の全ての要素を順に表示しています。

このコードを実行すると、次のような結果が得られます。

1 2 3 
4 5 6 
7 8 9

●C言語で行列操作を行う

C言語を使って行列の基本的な操作を行う方法について解説します。

○行列の加算

行列の加算は、対応する要素同士を足し合わせることで行います。

下記のコードは、二つの行列を加算するものです。

#include <stdio.h>

int main(){
    int i, j;
    int matrix1[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int matrix2[3][3] = {
        {9, 8, 7},
        {6, 5, 4},
        {3, 2, 1}
    };

    int result[3][3] = {0};

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            result[i][j] = matrix1[i][j] + matrix2[i][j];
        }
    }

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

このコードでは、matrix1matrix2という2つの行列を定義して、それぞれの対応する要素を足し合わせて新たな行列resultを生成しています。

このコードを実行すると、次のような結果が得られます。

10 10 10 
10 10 10 
10 10 10

○行列の減算

行列の減算も加算と同様に、対応する要素同士を引くことで行います。

下記のコードは、二つの行列を減算するものです。

#include <stdio.h>

int main(){
    int i, j;
    int matrix1[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int matrix2[3][3] = {
        {9, 8, 7},
        {6, 5, 4},
        {3, 2, 1}
    };

    int result[3][3] = {0};

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            result[i][j] = matrix1[i][j] - matrix2[i][j];
        }
    }

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

このコードでは、matrix1matrix2という2つの行列を定義して、それぞれの対応する要素を引き算して新たな行列resultを生成しています。

このコードを実行すると、次のような結果が得られます。

-8 -6 -4 
-2 0 2 
4 6 8

○行列の乗算

行列の乗算は、一つ目の行列の行と二つ目の行列の列を要素ごとに掛け合わせて合計したものを新たな行列の要素とします。

下記のコードは、二つの行列を乗算するものです。

#include <stdio.h>

int main(){
    int i, j, k;
    int matrix1[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int matrix2[3][3] = {
        {9, 8, 7},
        {6, 5, 4},
        {3, 2, 1}
    };

    int result[3][3] = {0};

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            for(k=0; k<3; k++){
                result[i][j] += matrix1[i][k] * matrix2[k][j];
            }
        }
    }

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

このコードでは、matrix1matrix2という2つの行列を定義して、それぞれの行と列を要素ごとに掛け合わせて新たな行列resultを生成しています。

このコードを実行すると、次のような結果が得られます。

30 24 18 
84 69 54 
138 114 90

○行列の転置

行列の転置とは、行列を主対角線に関して反転させる操作です。

下記のコードは、行列を転置するものです。

#include <stdio.h>

int main(){
    int i, j;
    int matrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int result[3][3] = {0};

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            result[j][i] = matrix[i][j];
        }
    }

    for(i=0; i<3; i++){
        for(j=0; j<3; j++){
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

このコードでは、matrixという行列を定義して、それを転置して新たな行列resultを生成しています。

このコードを実行すると、次のような結果が得られます。

1 4 7 
2 5 8 
3 6 9

○行列の逆行列

行列の逆行列について深く掘り下げてみましょう。

行列の逆行列は、その行列と掛け合わせると単位行列になるような行列のことを指します。

つまり、行列Aがあるとき、その逆行列をBとすると、AB=BA=I(Iは単位行列)となります。

しかし、全ての行列が逆行列を持つわけではなく、逆行列が存在するためには行列が正方行列であることと行列式が0でないことが条件です。

ここでは、3×3の行列の逆行列を求めるプログラムを紹介します。

ただし、このプログラムは単純化のためにエラーチェックを省略しています。

具体的には、与えられた行列が正方行列であることや行列式が0でないことなどを確認するステップは省略しています。

#include <stdio.h>

#define N 3

void get_cofactor(int mat[N][N], int temp[N][N], int p, int q, int n){
    int i = 0, j = 0;
    for (int row = 0; row < n; row++){
        for (int col = 0; col < n; col++){
            if (row != p && col != q){
                temp[i][j++] = mat[row][col];
                if (j == n - 1){
                    j = 0;
                    i++;
                }
            }
        }
    }
}

int determinant(int mat[N][N], int n){
    int D = 0;
    if (n == 1)
        return mat[0][0];

    int temp[N][N];
    int sign = 1;

    for (int f = 0; f < n; f++){
        get_cofactor(mat, temp, 0, f, n);
        D += sign * mat[0][f] * determinant(temp, n - 1);
        sign = -sign;
    }
    return D;
}

void adjoint(int mat[N][N], int adj[N][N]){
    if (N == 1){
        adj[0][0] = 1;
        return;
    }

    int sign = 1, temp[N][N];
    for (int i = 0; i < N; i++){
        for (int j = 0; j < N; j++){
            get_cofactor(mat, temp, i, j, N);
            sign = ((i + j) % 2 == 0)? 1: -1;
            adj[j][i] = (sign)*(determinant(temp, N - 1));
        }
    }
}

bool inverse(int mat[N][N], float inv[N][N]){
    int det = determinant(mat, N);
    if (det == 0){
        printf("Singular matrix, can't find its inverse");
        return false;
    }

    int adj[N][N];
    adjoint(mat, adj);

    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            inv[i][j] = adj[i][j]/(float)det;

    return true;
}

void display(float mat[N][N]){
    for (int i = 0; i < N; i++){
        for (int j = 0; j < N; j++)
            printf("%f ", mat[i][j]);
        printf("\n");
    }
}

int main(){
    int mat[N][N] = { {5, -2, 2}, 
                      {1, 1, -1}, 
                      {1, -1, 2} };

    float inv[N][N];
    if (inverse(mat, inv))
        display(inv);

    return 0;
}

このコードは、3×3行列の逆行列を求めます。具体的には、行列の余因子を求めるget_cofactor関数、行列式を求めるdeterminant関数、余因子行列の転置(随伴行列)を求めるadjoint関数、逆行列を求めるinverse関数、そして行列を表示するdisplay関数を定義しています。

プログラムは、定義した行列の逆行列を計算し、その結果を表示します。

このコードを実行すると、次のような結果が得られます。

0.250000 -0.500000 0.500000 
-0.250000 1.500000 0.500000 
-0.250000 0.500000 0.500000

これは、与えられた行列の逆行列を表しています。

逆行列を求めるプログラムは、通常、エラーチェックを含んでいます。

例えば、入力行列が正方行列であること、行列式が0でないことなどを確認します。

しかし、今回のプログラムでは簡略化のためこれらのチェックを省略しています。

このプログラムは3×3の行列の逆行列を求めますが、それ以外のサイズの行列に対しては使用できません。

また、行列のサイズが大きくなると、計算に時間がかかる可能性があります。

この問題を解決する一つの方法は、行列の分解を使用することです。

例えば、LU分解やQR分解などの方法がありますが、これらの詳細な説明は本記事の範囲を超えています。

●C言語での行列操作の応用例

さて、ここまでの内容で、行列操作の基本的な理論とC言語でのその表現方法、操作法について学んできました。

では、これらの知識は現実世界でどのように応用されるのでしょうか。

ここでは、C言語での行列操作の具体的な応用例をいくつかご紹介します。

○行列を使ったシステムの解析

まず一つ目の応用例として、システムの解析が挙げられます。

例えば、電気回路や経済モデルなど、多くのシステムは複数の要素が相互に影響を及ぼす複雑な関係性で構成されています。

これらのシステムを解析する際に、行列は非常に有用なツールとなります。

具体的なコードを見てみましょう。

ここでは、連立一次方程式の解を求める例を紹介します。

このコードでは、行列の逆行列を求める操作を利用しています。

まずは、求めたい連立一次方程式の係数行列と、それに対応する結果のベクトルを初期化します。

#include<stdio.h>
#include<math.h>

// 連立一次方程式の解を求める関数
void solveLinearEquations(double a[3][3], double b[3]) {
    // ピボット化、前進消去、後退代入の手順を踏んで解を求めます。

    // 省略...
}

int main(void) {
    // 連立一次方程式の係数行列
    double a[3][3] = {
        {3.0, 2.0, 1.0},
        {2.0, 3.0, 2.0},
        {1.0, 2.0, 3.0}
    };

    // それに対応する結果のベクトル
    double b[3] = {10.0, 14.0, 10.0};

    solveLinearEquations(a, b);

    // 解の表示
    for (int i = 0; i < 3; i++) {
        printf("x%d = %f\n", i + 1, b[i]);
    }

    return 0;
}

このコードでは、行列aを使って3次の連立一次方程式を表現しています。

そして、行列の逆行列を求める操作を通じて、各変数の解を得ます。

この例では、3つの変数x1、x2、x3の解がそれぞれどのように求まるのかを表示しています。

○行列を使った画像処理

行列操作のもう一つの重要な応用例として、画像処理があります。

デジタル画像は、各ピクセルが特定の色情報を持つ2次元の配列、すなわち行列として表現されます。

このため、行列操作は画像処理の多くの手法で使われます。

例えば、画像の輝度調整、色調補正、平滑化、エッジ検出など、あらゆる操作が行列演算を通じて行われます。

下記のコードは、グレースケール画像の明るさを調整するシンプルな例を表しています。

#include<stdio.h>

// 画像の明るさを調整する関数
void adjustBrightness(int image[256][256], int width, int height, int brightness) {
    // 画像の全てのピクセルについて、明るさを調整します。
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            image[y][x] += brightness;
            if (image[y][x] > 255) {
                image[y][x] = 255;
            } else if (image[y][x] < 0) {
                image[y][x] = 0;
            }
        }
    }
}

int main(void) {
    // サンプルのグレースケール画像
    int image[256][256];

    // 省略...

    // 明るさを調整
    adjustBrightness(image, 256, 256, 50);

    return 0;
}

このコードでは、2次元配列imageに格納されたグレースケール画像の明るさを調整しています。

各ピクセルの値は0から255の範囲で表され、それぞれ黒から白を示しています。

adjustBrightness関数は、画像の全ピクセルに対して一定の値を加えることで、画像全体の明るさを調整します。

値が255を超えたり0未満になったりすると色情報が溢れるため、それを防ぐために範囲を制限しています。

●注意点と対処法

C言語で行列操作を行う際にはいくつかの注意点があります。

まず、C言語には配列の範囲外を参照するというエラーが起こる可能性があります。

行列のサイズを超えた位置を参照しようとすると、プログラムは予期せぬ動作を引き起こすかもしれません。

下記のコードは、範囲外エラーを表すものです。

#include <stdio.h>

int main() {
    int matrix[2][2] = {{1, 2}, {3, 4}};

    // 行列の範囲外を参照
    printf("%d\n", matrix[2][2]);

    return 0;
}

このコードでは、2×2行列を作成し、存在しないインデックス matrix[2][2] を参照しています。

結果として、予期せぬ動作が発生します。

このようなエラーを避けるためには、常に配列の範囲を超えないように注意する必要があります。

また、行列計算の際には、行列の形状が一致しないとエラーが発生する可能性があります。

たとえば、加算や減算は同じ形状の行列同士でしか行えませんし、乗算では左側の行列の列数と右側の行列の行数が一致していなければなりません。

下記のコードは、形状が一致しない行列に対する加算を試みています。

#include <stdio.h>

int main() {
    int matrix1[2][2] = {{1, 2}, {3, 4}};
    int matrix2[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

    int result[2][2];
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            result[i][j] = matrix1[i][j] + matrix2[i][j];
        }
    }

    // 結果の出力
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

このコードでは、2×2行列と3×3行列の加算を試みていますが、形状が一致しないためエラーが発生します。

このような問題を防ぐためには、行列計算を行う前に行列の形状を確認することが重要です。

まとめ

この記事では、C言語で行列操作を行うための基本的な知識と具体的な方法について解説しました。

行列は様々な分野で使われており、行列操作の知識はプログラミングスキルを大幅に広げることができます。

また、C言語は学びやすさと汎用性から初学者におすすめの言語であり、C言語で行列操作を学ぶことは非常に有益です。

しかし、行列操作はコードのエラーを生みやすいものでもあります。

配列の範囲外エラーや、行列の形状が一致しないといった問題が生じやすいです。それらを避けるためには、行列のサイズを管理し、行列計算を行う前に形状を確認することが必要です。

C言語で行列操作を学び始めると、新たな可能性が見えてきます。

是非、この知識を活用して、あなたのプログラミングスキルをさらに向上させてください。