[C++] 二次元配列のvectorに要素を追加する方法

C++で二次元配列を表現する際、std::vectorを使用することが一般的です。二次元配列はstd::vector<std::vector<T>>として定義されます。

要素を追加するには、まず外側のvectorに対してpush_backを使用して新しいvectorを追加します。

その後、内側のvectorに対してpush_backを使用して要素を追加します。

この方法により、動的に行や列を追加することが可能です。

この記事でわかること
  • 二次元vectorに要素を追加する方法
  • 二次元vectorの要素へのアクセス方法
  • 二次元vectorを用いた行列の応用例
  • 二次元vectorのメモリ管理の基本

目次から探す

二次元vectorに要素を追加する方法

C++の二次元vectorは、動的にサイズを変更できる便利なデータ構造です。

ここでは、二次元vectorに要素を追加する方法をいくつか紹介します。

push_backを使った追加方法

push_backは、vectorの末尾に新しい要素を追加するためのメソッドです。

二次元vectorの場合、各行に対してpush_backを使用して要素を追加できます。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言
    std::vector<std::vector<int>> matrix;
    // 新しい行を追加
    matrix.push_back(std::vector<int>());
    // 行の末尾に要素を追加
    matrix[0].push_back(1);
    matrix[0].push_back(2);
    matrix[0].push_back(3);
    // 結果を表示
    for (const auto& row : matrix) {
        for (int value : row) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
1 2 3

この例では、まず新しい行を追加し、その行に対してpush_backを使って要素を追加しています。

これにより、行ごとに異なる長さのvectorを持つことができます。

insertを使った特定位置への追加

insertメソッドを使用すると、vectorの特定の位置に要素を挿入することができます。

二次元vectorでも同様に、特定の行の任意の位置に要素を挿入できます。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言と初期化
    std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}};
    // 1行目の2番目の位置に10を挿入
    matrix[0].insert(matrix[0].begin() + 1, 10);
    // 結果を表示
    for (const auto& row : matrix) {
        for (int value : row) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
1 10 2 3
4 5 6

この例では、1行目の2番目の位置に10を挿入しています。

insertを使うことで、任意の位置に要素を追加することが可能です。

emplace_backを使った効率的な追加

emplace_backは、push_backと似ていますが、オブジェクトを直接構築するため、効率的に要素を追加できます。

特に、オブジェクトのコピーが発生しないため、パフォーマンスが向上します。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言
    std::vector<std::vector<int>> matrix;
    // 新しい行を追加
    matrix.emplace_back(); // ここで新しい行を直接構築
    // 行の末尾に要素を追加
    matrix[0].emplace_back(7);
    matrix[0].emplace_back(8);
    matrix[0].emplace_back(9);
    // 結果を表示
    for (const auto& row : matrix) {
        for (int value : row) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
7 8 9

この例では、emplace_backを使って新しい行を追加し、その行に要素を追加しています。

emplace_backは、特にオブジェクトの構築が必要な場合に有効です。

二次元vectorの要素アクセス

二次元vectorの要素にアクセスする方法は複数あります。

ここでは、インデックス、atメソッド、イテレータを使ったアクセス方法を紹介します。

インデックスを使ったアクセス方法

インデックスを使ったアクセスは、最も一般的で簡単な方法です。

行と列のインデックスを指定して要素にアクセスします。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言と初期化
    std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    // インデックスを使って要素にアクセス
    int value = matrix[1][2]; // 2行目の3列目の要素を取得
    // 結果を表示
    std::cout << "取得した要素: " << value << std::endl;
    return 0;
}
取得した要素: 6

この方法はシンプルで高速ですが、範囲外のインデックスを指定すると未定義の動作を引き起こす可能性があります。

atメソッドを使った安全なアクセス

atメソッドを使うと、範囲外アクセスを防ぐことができます。

範囲外のインデックスを指定した場合、例外がスローされます。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言と初期化
    std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    try {
        // atメソッドを使って要素にアクセス
        int value = matrix.at(1).at(2); // 2行目の3列目の要素を取得
        // 結果を表示
        std::cout << "取得した要素: " << value << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "範囲外アクセス: " << e.what() << std::endl;
    }
    return 0;
}
取得した要素: 6

atメソッドを使うことで、範囲外アクセスを安全に防ぐことができ、デバッグ時に役立ちます。

イテレータを使ったアクセス

イテレータを使うと、vectorの要素を順番に処理することができます。

特に、全要素を走査する場合に便利です。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言と初期化
    std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    // イテレータを使って要素にアクセス
    for (auto rowIt = matrix.begin(); rowIt != matrix.end(); ++rowIt) {
        for (auto colIt = rowIt->begin(); colIt != rowIt->end(); ++colIt) {
            std::cout << *colIt << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
1 2 3
4 5 6
7 8 9

イテレータを使うことで、vectorの要素を柔軟に操作することができ、特に複雑な操作を行う際に有用です。

二次元vectorの応用例

二次元vectorは、行列のようなデータ構造を扱うのに非常に便利です。

ここでは、二次元vectorを用いた行列の転置、加算、スカラー倍の応用例を紹介します。

行列の転置

行列の転置とは、行と列を入れ替える操作です。

二次元vectorを使って行列の転置を行う方法を示します。

#include <iostream>
#include <vector>
std::vector<std::vector<int>> transpose(const std::vector<std::vector<int>>& matrix) {
    if (matrix.empty()) return {};
    std::vector<std::vector<int>> transposed(matrix[0].size(), std::vector<int>(matrix.size()));
    for (size_t i = 0; i < matrix.size(); ++i) {
        for (size_t j = 0; j < matrix[i].size(); ++j) {
            transposed[j][i] = matrix[i][j];
        }
    }
    return transposed;
}
int main() {
    // 二次元vectorの宣言と初期化
    std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}};
    // 行列の転置
    std::vector<std::vector<int>> transposedMatrix = transpose(matrix);
    // 結果を表示
    for (const auto& row : transposedMatrix) {
        for (int value : row) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
1 4
2 5
3 6

この例では、元の行列の行と列を入れ替えて新しい行列を作成しています。

行列の加算

行列の加算は、同じ位置の要素同士を足し合わせる操作です。

二次元vectorを使って行列の加算を行う方法を示します。

#include <iostream>
#include <vector>
std::vector<std::vector<int>> addMatrices(const std::vector<std::vector<int>>& matrix1, const std::vector<std::vector<int>>& matrix2) {
    std::vector<std::vector<int>> result(matrix1.size(), std::vector<int>(matrix1[0].size()));
    for (size_t i = 0; i < matrix1.size(); ++i) {
        for (size_t j = 0; j < matrix1[i].size(); ++j) {
            result[i][j] = matrix1[i][j] + matrix2[i][j];
        }
    }
    return result;
}
int main() {
    // 二次元vectorの宣言と初期化
    std::vector<std::vector<int>> matrix1 = {{1, 2, 3}, {4, 5, 6}};
    std::vector<std::vector<int>> matrix2 = {{7, 8, 9}, {10, 11, 12}};
    // 行列の加算
    std::vector<std::vector<int>> sumMatrix = addMatrices(matrix1, matrix2);
    // 結果を表示
    for (const auto& row : sumMatrix) {
        for (int value : row) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
8 10 12
14 16 18

この例では、同じ位置の要素を足し合わせて新しい行列を作成しています。

行列のスカラー倍

行列のスカラー倍は、行列のすべての要素に同じスカラー値を掛ける操作です。

二次元vectorを使って行列のスカラー倍を行う方法を示します。

#include <iostream>
#include <vector>
std::vector<std::vector<int>> scalarMultiply(const std::vector<std::vector<int>>& matrix, int scalar) {
    std::vector<std::vector<int>> result(matrix.size(), std::vector<int>(matrix[0].size()));
    for (size_t i = 0; i < matrix.size(); ++i) {
        for (size_t j = 0; j < matrix[i].size(); ++j) {
            result[i][j] = matrix[i][j] * scalar;
        }
    }
    return result;
}
int main() {
    // 二次元vectorの宣言と初期化
    std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}};
    // 行列のスカラー倍
    int scalar = 3;
    std::vector<std::vector<int>> scaledMatrix = scalarMultiply(matrix, scalar);
    // 結果を表示
    for (const auto& row : scaledMatrix) {
        for (int value : row) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
3 6 9
12 15 18

この例では、行列のすべての要素にスカラー値を掛けて新しい行列を作成しています。

二次元vectorのメモリ管理

C++の二次元vectorは、動的にメモリを管理するため、プログラマが直接メモリ管理を行う必要がありません。

しかし、効率的なプログラムを作成するためには、メモリ管理の基本を理解しておくことが重要です。

メモリの自動管理

二次元vectorは、C++のSTL(Standard Template Library)の一部であり、メモリの自動管理を行います。

vectorは必要に応じてメモリを確保し、スコープを抜けると自動的に解放されます。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言
    std::vector<std::vector<int>> matrix;
    // メモリの自動管理
    matrix.push_back({1, 2, 3});
    matrix.push_back({4, 5, 6});
    // vectorがスコープを抜けるときにメモリが自動的に解放される
    return 0;
}

この例では、matrixがスコープを抜けるときに、vectorが自動的にメモリを解放します。

プログラマはメモリの解放を心配する必要がありません。

メモリの再確保とその影響

vectorは、要素が追加されると必要に応じてメモリを再確保します。

再確保はコストがかかる操作であり、頻繁に発生するとパフォーマンスに影響を与える可能性があります。

#include <iostream>
#include <vector>
int main() {
    // 二次元vectorの宣言
    std::vector<std::vector<int>> matrix;
    // メモリの再確保を最小限にするために、事前に容量を予約
    matrix.reserve(10); // 10行分のメモリを予約
    for (int i = 0; i < 10; ++i) {
        matrix.push_back({i, i + 1, i + 2});
    }
    std::cout << "行数: " << matrix.size() << std::endl;
    return 0;
}

この例では、reserveを使って事前にメモリを予約することで、再確保の回数を減らし、パフォーマンスを向上させています。

メモリリークを防ぐ方法

二次元vectorを使用する場合、通常はメモリリークの心配はありませんが、他の動的メモリ管理と組み合わせる場合は注意が必要です。

例えば、ポインタをvectorに格納する場合、適切にメモリを解放する必要があります。

#include <iostream>
#include <vector>
int main() {
    // ポインタを格納する二次元vectorの宣言
    std::vector<std::vector<int*>> matrix;
    // メモリの割り当て
    for (int i = 0; i < 3; ++i) {
        std::vector<int*> row;
        for (int j = 0; j < 3; ++j) {
            row.push_back(new int(i * j)); // 動的にメモリを割り当て
        }
        matrix.push_back(row);
    }
    // メモリの解放
    for (auto& row : matrix) {
        for (int* ptr : row) {
            delete ptr; // 動的に割り当てたメモリを解放
        }
    }
    return 0;
}

この例では、newで動的に割り当てたメモリをdeleteで解放することで、メモリリークを防いでいます。

ポインタを使用する場合は、必ずメモリを適切に解放することが重要です。

よくある質問

二次元vectorのサイズを変更するにはどうすればいいですか?

二次元vectorのサイズを変更するには、resizeメソッドを使用します。

resizeを使うことで、行や列の数を変更できます。

例えば、行の数を変更するには、外側のvectorに対してresizeを呼び出します。

列の数を変更するには、各行のvectorに対してresizeを呼び出します。

例:matrix.resize(5); // 行数を5に変更

二次元vectorの特定の行や列を削除する方法は?

特定の行を削除するには、eraseメソッドを使用します。

行を削除する場合、外側のvectorに対してeraseを呼び出します。

特定の列を削除するには、各行のvectorに対してeraseを呼び出す必要があります。

例:matrix.erase(matrix.begin() + 2); // 3行目を削除

二次元vectorの要素を効率的に検索する方法は?

二次元vectorの要素を効率的に検索するには、std::findを使用します。

std::findは、指定された範囲内で要素を検索し、見つかった場合はそのイテレータを返します。

見つからない場合は、範囲の終わりのイテレータを返します。

例:auto it = std::find(matrix[0].begin(), matrix[0].end(), 5); // 1行目で5を検索

検索の効率を上げるためには、データがソートされている場合、std::binary_searchを使用することも検討できます。

まとめ

この記事では、C++の二次元vectorにおける要素の追加方法やアクセス方法、応用例、メモリ管理について詳しく解説しました。

二次元vectorを効果的に活用するための基本的な操作から応用的なテクニックまでをカバーし、プログラムの効率化や安全性の向上に役立つ情報を提供しました。

これを機に、実際のプログラムで二次元vectorを活用し、より複雑なデータ構造の操作に挑戦してみてください。

当サイトはリンクフリーです。出典元を明記していただければ、ご自由に引用していただいて構いません。

関連カテゴリーから探す

  • URLをコピーしました!
目次から探す