1.2.1. 配列

1. 概要

 配列は、プログラミングにおいて最も基本的かつ重要なデータ構造の一つです。配列とは、同じ型のデータ要素を連続的なメモリ領域に格納する構造であり、各要素にはインデックス(添字)を用いてアクセスすることができます。このインデックスによって、配列内の特定の位置にあるデータを効率的に格納したり取り出したりすることが可能となります。

 応用情報処理技術者試験においては、配列の基本概念や操作方法を理解することが重要であり、特に「データの格納方法」と「取出し方法」に関する知識が問われます。また、静的配列動的配列の違い、多次元配列の扱い方なども理解しておく必要があります。

2. 詳細説明

2.1. 配列の基本構造

 配列は、同じデータ型の要素を連続したメモリ領域に格納するデータ構造です。各要素はインデックスによって識別され、通常は0から始まる整数値が使用されます。

配列の一般的な表現:array[0], array[1], array[2], ... array[n-1]

 配列のメモリ上の配置は、以下の特徴を持ちます:

  1. 連続した配置:各要素は隣接するメモリ領域に格納される
  2. 固定サイズのデータ型:各要素のメモリサイズは同一
  3. ランダムアクセス:インデックスを指定することで任意の要素に直接アクセス可能

2.1.1. メモリアドレスの計算

 配列の重要な特徴は、インデックスから要素のメモリアドレスを簡単に計算できることです。配列の先頭アドレスをbase_address、各要素のサイズをelement_sizeとすると、インデックスiの要素のアドレスは次の式で求められます:

address_of_element[i] = base_address + (i × element_size)

 この計算方法により、配列はO(1)の時間複雑度で任意の要素にアクセス可能となります。これは配列の最大の利点の一つです。

2.2. 静的配列と動的配列

2.2.1. 静的配列

 静的配列は、プログラムの実行前にサイズが決定され、実行中にサイズを変更できない配列です。

特徴:

  • コンパイル時にメモリ割り当てが行われる
  • メモリの使用効率が良い
  • 固定サイズのため、データ量が予測可能な場合に適している
  • 要素の追加や削除ができない
// C言語での静的配列の宣言例
int numbers[100];  // 100個の整数を格納する配列

2.2.2. 動的配列

 動的配列は、プログラムの実行中にサイズを変更できる配列です。

特徴:

  • 実行時にメモリ割り当てが行われる
  • 必要に応じてサイズを拡張・縮小できる
  • メモリの再割り当てが必要な場合がある
  • データ量が予測困難な場合に適している
// C言語での動的配列の宣言例
int* numbers = (int*)malloc(100 * sizeof(int));  // 100個の整数を格納する動的配列
// 使用後は解放する必要がある
free(numbers);
特性 静的配列 動的配列
メモリ割り当て時期 コンパイル時 実行時
サイズ変更 不可 可能
メモリ効率 高い(オーバーヘッド少) やや低い(管理オーバーヘッドあり)
メモリ位置 スタック領域 ヒープ領域
主な使用場面 データ量が固定的な場合 データ量が可変的な場合
宣言例(C言語) int arr[10]; int* arr = malloc(10 * sizeof(int));
メモリ管理 自動(スコープ終了時に解放) 手動(明示的に解放が必要)

表1: 静的配列と動的配列の比較

2.2.3. 言語による違い

 プログラミング言語によって、配列の実装や取り扱いが異なります。

  • C/C++: 静的配列と動的配列の両方をサポート。低レベルなメモリ管理が可能
  • Java: すべての配列はオブジェクトとして実装され、サイズは固定だが動的に生成可能
  • Python: リストとして実装され、動的にサイズ変更可能
  • JavaScript: 配列はオブジェクトの一種で、動的にサイズ変更や型の混在が可能

2.3. 多次元配列

 多次元配列は、2つ以上の次元を持つ配列構造です。最も一般的なのは2次元配列で、行と列の形式でデータを格納します。

2.3.1. 2次元配列

 2次元配列は、行と列の形式でデータを格納します。例えば、array[i][j]という形式でアクセスし、iは行、jは列を指定します。

// C言語での2次元配列の宣言例
int matrix[3][4];  // 3行4列の行列

メモリ上の格納方法:

  • 行優先方式:行ごとに連続してメモリに格納(C言語など)
  • 列優先方式:列ごとに連続してメモリに格納(FORTRAN言語など)

 行優先方式では行が連続したメモリ領域に配置され(array[0][0], array[0][1], array[0][2], …)、列優先方式では列が連続したメモリ領域に配置されます(array[0][0], array[1][0], array[2][0], …)。プログラミング言語によって採用している方式が異なるため、特に他言語との連携時には注意が必要です。

2.3.2. 多次元配列の一般化

 3次元以上の配列も同様に定義できます。例えば、3次元配列はarray[i][j][k]という形式でアクセスします。3次元配列は立体的なデータ(例:3Dゲームの空間座標や時系列で変化する2次元データなど)を表現するのに適しています。

// C言語での3次元配列の宣言例
int cube[2][3][4];  // 2x3x4の3次元配列

 多次元配列のメモリ上の配置も、基本的には1次元的に連続して格納されます。C言語の場合、最も右のインデックスが最も速く変化します。n次元配列のメモリアドレス計算式は以下のようになります:

// 3次元配列[i][j][k]の場合(C言語、行優先)
address = base_address + ((i * dim2 * dim3) + (j * dim3) + k) * sizeof(element)

 ここで、dim2は2次元目のサイズ、dim3は3次元目のサイズを表します。

2.4. 配列の操作

2.4.1. データの格納(代入)

 配列へのデータ格納は、インデックスを指定して行います。

array[0] = 10;  // 配列の最初の要素に10を格納
matrix[1][2] = 5;  // 2次元配列の1行2列目に5を格納

2.4.2. データの取出し(参照)

 配列からのデータ取出しも、インデックスを指定して行います。

int value = array[3];  // 配列の4番目の要素を取り出す
int element = matrix[0][1];  // 2次元配列の0行1列目の要素を取り出す

2.4.3. 走査(ループ処理)

 配列の全要素に対して処理を行う場合、通常はループ構造を使用します。

// 1次元配列の走査
for (int i = 0; i < 10; i++) {
    process(array[i]);
}

// 2次元配列の走査
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        process(matrix[i][j]);
    }
}

2.4.4. 配列操作の計算量

 配列操作の計算量(時間複雑度)を理解することは、効率的なアルゴリズム設計において重要です。配列の主な操作とその計算量は以下のとおりです:

操作 時間複雑度 説明
要素へのアクセス O(1) インデックスから直接メモリアドレスを計算するため定数時間
要素の代入 O(1) 特定位置への値の格納は定数時間
要素の検索 O(n) 配列がソートされていない場合、最悪の場合全要素の探索が必要
二分探索 O(log n) 配列がソートされている場合に使用可能
全要素の走査 O(n) 1次元配列の場合、n個の要素すべてを処理
m×n行列の走査 O(m×n) 2次元配列の場合、m行n列の全要素を処理
要素の挿入(先頭) O(n) 既存の要素をシフトする必要があるため線形時間
要素の挿入(末尾) O(1)〜O(n) 動的配列の場合、容量拡張が必要なことがある
要素の削除 O(n) 削除後の要素を詰める処理が必要

表2: 配列操作の計算量

 配列の最大の利点は要素へのランダムアクセスが O(1) で行えることですが、要素の挿入や削除には位置によって効率が異なる点に注意が必要です。特に大規模なデータを扱う場合は、これらの計算量が処理性能に大きく影響します。

3. 応用例

3.1. 実際のプログラミングでの応用

3.1.1. データ集計と統計処理

 配列は、大量のデータを格納し、統計処理を行うのに適しています。例えば、売上データの集計や平均値・標準偏差の計算などに利用されます。

// 配列を使用した平均値計算の例
double calculateAverage(int values[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += values[i];
    }
    return (double)sum / size;
}

3.1.2. 画像処理

 画像処理では、ピクセルデータを2次元配列として扱います。各ピクセルの色情報を格納し、フィルタ処理や画像変換を行います。

// 画像の輝度反転処理の例(擬似コード)
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        image[y][x] = 255 - image[y][x];  // 輝度値の反転
    }
}

3.1.3. 行列演算

 数学的な行列演算は、多次元配列を使用して実装されます。これは科学計算や機械学習アルゴリズムの基礎となります。

// 行列の加算例
for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
        result[i][j] = matrixA[i][j] + matrixB[i][j];
    }
}

3.2. 実務での活用例

3.2.1. データベース管理システム

 データベースの内部実装では、インデックスやテーブルデータを効率的に管理するために配列構造が使用されます。特に、インメモリデータベースでは、データを配列として格納し高速にアクセスします。

3.2.2. 表計算ソフトウェア

 Excelなどのスプレッドシートアプリケーションでは、セルデータを2次元配列として管理し、計算式や関数を適用します。

3.2.3. グラフィックスプログラミング

 3Dゲームや視覚化ソフトウェアでは、頂点データや座標情報を多次元配列として扱い、レンダリングや物理演算に利用します。

4. 例題

例題1:1次元配列の操作

問題: 10個の整数を格納する配列を宣言し、各要素に1から10までの値を代入した後、配列内の偶数のみの合計を求めるプログラムを作成してください。

#include <stdio.h>

int main() {
    int numbers[10];  // 静的配列の宣言
    int sum = 0;
    
    // 配列に値を代入
    for (int i = 0; i < 10; i++) {
        numbers[i] = i + 1;
    }
    
    // 偶数の合計を計算
    for (int i = 0; i < 10; i++) {
        if (numbers[i] % 2 == 0) {  // 偶数かどうかの判定
            sum += numbers[i];
        }
    }
    
    printf("偶数の合計: %d\n", sum);  // 結果: 30 (2+4+6+8+10)
    return 0;
}

例題2:2次元配列の操作

問題: 3×3の2次元配列を宣言し、各要素に行番号と列番号の和を代入した後、対角線上(左上から右下)の要素の和を求めるプログラムを作成してください。

#include <stdio.h>

int main() {
    int matrix[3][3];  // 2次元配列の宣言
    int diagonalSum = 0;
    
    // 配列に値を代入
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            matrix[i][j] = i + j;
        }
    }
    
    // 対角線上の要素の和を計算
    for (int i = 0; i < 3; i++) {
        diagonalSum += matrix[i][i];
    }
    
    printf("対角線上の要素の和: %d\n", diagonalSum);  // 結果: 3 (0+1+2)
    return 0;
}

例題3:静的配列と動的配列

問題: ユーザーから整数nを入力として受け取り、サイズnの整数配列を動的に生成し、各要素にインデックスの2乗を代入した後、全要素の平均値を計算するプログラムを作成してください。

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

int main() {
    int n;
    printf("配列のサイズを入力してください: ");
    scanf("%d", &n);
    
    // 動的配列の宣言
    int* numbers = (int*)malloc(n * sizeof(int));
    if (numbers == NULL) {
        printf("メモリ割り当てエラー\n");
        return 1;
    }
    
    int sum = 0;
    
    // 配列に値を代入
    for (int i = 0; i < n; i++) {
        numbers[i] = i * i;  // インデックスの2乗
        sum += numbers[i];
    }
    
    double average = (double)sum / n;
    printf("要素の平均値: %.2f\n", average);
    
    // 動的配列の解放
    free(numbers);
    return 0;
}

例題4:多次元配列の応用

問題: 2×3×2の3次元配列を宣言し、各要素に3つのインデックスの積を代入した後、全要素の合計を求めるプログラムを作成してください。さらに、この3次元配列のメモリレイアウトとアクセス方法について説明してください。

#include <stdio.h>

int main() {
    int cube[2][3][2];  // 3次元配列の宣言
    int total = 0;
    
    // 配列に値を代入
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 2; k++) {
                cube[i][j][k] = i * j * k;
                total += cube[i][j][k];
            }
        }
    }
    
    // 結果の表示
    printf("全要素の合計: %d\n", total);
    
    // メモリレイアウトの説明
    printf("\n3次元配列のメモリレイアウト:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 2; k++) {
                printf("cube[%d][%d][%d] = %d (アドレス: %p)\n", 
                       i, j, k, cube[i][j][k], &cube[i][j][k]);
            }
        }
    }
    
    return 0;
}

メモリレイアウトの説明: C言語では、3次元配列は「配列の配列の配列」として実装されており、メモリ上では行優先(row-major)順に配置されます。つまり、最右のインデックス(k)が最も速く変化し、最左のインデックス(i)が最も遅く変化します。

メモリアドレスの計算式:

address = base_address + ((i * 3 * 2) + (j * 2) + k) * sizeof(element)

3次元配列をメモリ上で一次元的に並べた場合、以下の順序で要素が配置されます:

cube[0][0][0], cube[0][0][1], cube[0][1][0], cube[0][1][1], cube[0][2][0], cube[0][2][1],
cube[1][0][0], cube[1][0][1], cube[1][1][0], cube[1][1][1], cube[1][2][0], cube[1][2][1]

この問題の計算結果:

  • 合計:0 + 0 + 0 + 1 + 0 + 2 + 0 + 0 + 0 + 1 + 0 + 2 = 6

5. まとめ

 配列は、同じ型のデータを連続したメモリ領域に格納する基本的なデータ構造であり、プログラミングにおいて非常に重要な役割を果たします。

主なポイント:

  1. 基本構造: 配列は同じデータ型の要素を連続したメモリに格納し、インデックスを使って各要素にアクセスします。
  2. 静的配列と動的配列:
    • 静的配列はコンパイル時にサイズが決定され、実行中に変更できません。
    • 動的配列は実行時にメモリを割り当て、必要に応じてサイズを変更できます。
  3. 多次元配列: 2次元以上の配列で、行列や立体構造などの複雑なデータを表現します。
  4. データの操作:
    • 格納(代入): インデックスを指定して値を設定します。
    • 取出し(参照): インデックスを指定して値を取得します。
    • 走査: ループ構造を使って全要素に処理を適用します。
  5. 応用例: 配列は統計処理、画像処理、行列演算、データベース、表計算ソフトウェアなど様々な分野で活用されています。

 応用情報処理技術者試験では、配列の基本概念と操作方法を理解し、実際のプログラミング場面での活用方法を把握していることが求められます。特に静的配列と動的配列の違い、多次元配列の扱い方など、メモリ管理の観点からの理解が重要です。