[C++] std::stackの使い方について詳しく解説

std::stackは、C++標準ライブラリで提供されるLIFO(Last In, First Out)構造を持つコンテナアダプタです。

このクラスは、デフォルトでstd::dequeを基に実装されていますが、std::vectorstd::listを基にすることも可能です。

主なメンバ関数には、要素を追加するpush()、要素を削除するpop()、最上位の要素を参照するtop()、スタックが空かどうかを確認するempty()、および要素数を取得するsize()があります。

これらの関数を活用することで、効率的にスタック操作を行うことができます。

この記事でわかること
  • std::stackの基本的な操作方法
  • スタックの内部実装と使用するコンテナ
  • 深さ優先探索やUndo機能などの応用例
  • std::stackと他のデータ構造との違い
  • 使用時の注意点とメモリ効率について

目次から探す

std::stackとは

std::stackの概要

std::stackは、C++の標準ライブラリに含まれるコンテナアダプタの一つで、LIFO(Last In, First Out)方式でデータを管理します。

つまり、最後に追加した要素が最初に取り出される構造です。

スタックは、特定の操作(要素の追加、削除、参照)を効率的に行うために設計されています。

スタックの基本的な概念

スタックは、以下の基本操作を持っています:

スクロールできます
操作名説明
pushスタックの上に要素を追加する
popスタックの上から要素を削除する
topスタックの上の要素を参照する
sizeスタックに含まれる要素の数を取得する
emptyスタックが空かどうかを確認する

これらの操作により、スタックはデータの管理を簡単に行うことができます。

std::stackの特徴

std::stackにはいくつかの特徴があります:

  • コンテナアダプタ: std::stackは他のコンテナ(例えば、std::vectorstd::deque)を基にして動作します。

デフォルトではstd::dequeが使用されます。

  • 制限された操作: スタックは、特定の操作のみを提供し、他の操作は制限されています。

これにより、データの整合性が保たれます。

  • 効率的なメモリ管理: スタックは、要素の追加や削除がO(1)の時間で行えるため、効率的なメモリ管理が可能です。

これらの特徴により、std::stackは多くのアルゴリズムやデータ構造で広く利用されています。

std::stackの基本操作

std::stackの宣言と初期化

std::stackを使用するには、まずヘッダーファイルをインクルードし、スタックを宣言します。

以下は、std::stackの宣言と初期化の例です。

#include <stack>
int main() {
    std::stack<int> myStack; // 整数型のスタックを宣言
    return 0;
}

要素の追加(push)

スタックに要素を追加するには、pushメソッドを使用します。

以下の例では、整数をスタックに追加しています。

#include <stack>
#include <iostream>
int main() {
    std::stack<int> myStack;
    myStack.push(10); // 10を追加
    myStack.push(20); // 20を追加
    myStack.push(30); // 30を追加
    std::cout << "スタックの上の要素: " << myStack.top() << std::endl; // 30
    return 0;
}
スタックの上の要素: 30

要素の削除(pop)

スタックから要素を削除するには、popメソッドを使用します。

以下の例では、スタックの上の要素を削除しています。

#include <stack>
#include <iostream>
int main() {
    std::stack<int> myStack;
    myStack.push(10);
    myStack.push(20);
    myStack.push(30);
    myStack.pop(); // 上の要素(30)を削除
    std::cout << "スタックの上の要素: " << myStack.top() << std::endl; // 20
    return 0;
}
スタックの上の要素: 20

先頭要素の参照(top)

スタックの上の要素を参照するには、topメソッドを使用します。

以下の例では、スタックの上の要素を表示しています。

#include <stack>
#include <iostream>
int main() {
    std::stack<int> myStack;
    myStack.push(10);
    myStack.push(20);
    myStack.push(30);
    std::cout << "スタックの上の要素: " << myStack.top() << std::endl; // 30
    return 0;
}
スタックの上の要素: 30

スタックのサイズを取得(size)

スタックに含まれる要素の数を取得するには、sizeメソッドを使用します。

以下の例では、スタックのサイズを表示しています。

#include <stack>
#include <iostream>
int main() {
    std::stack<int> myStack;
    myStack.push(10);
    myStack.push(20);
    myStack.push(30);
    std::cout << "スタックのサイズ: " << myStack.size() << std::endl; // 3
    return 0;
}
スタックのサイズ: 3

スタックが空かどうかの確認(empty)

スタックが空かどうかを確認するには、emptyメソッドを使用します。

以下の例では、スタックが空かどうかをチェックしています。

#include <stack>
#include <iostream>
int main() {
    std::stack<int> myStack;
    if (myStack.empty()) {
        std::cout << "スタックは空です。" << std::endl; // スタックは空です。
    }
    myStack.push(10);
    if (!myStack.empty()) {
        std::cout << "スタックは空ではありません。" << std::endl; // スタックは空ではありません。
    }
    return 0;
}
スタックは空です。
スタックは空ではありません。

std::stackの使い方の例

基本的な使い方の例

std::stackの基本的な使い方を示す簡単なプログラムです。

この例では、整数をスタックに追加し、削除する操作を行います。

#include <stack>
#include <iostream>
int main() {
    std::stack<int> myStack;
    // 要素の追加
    myStack.push(1);
    myStack.push(2);
    myStack.push(3);
    // スタックの要素を表示
    while (!myStack.empty()) {
        std::cout << myStack.top() << std::endl; // 3, 2, 1
        myStack.pop();
    }
    return 0;
}
3
2
1

数値の逆順表示

スタックを使用して、数値の逆順表示を行うプログラムです。

ユーザーから入力された数値をスタックに追加し、逆順で表示します。

#include <stack>
#include <iostream>
int main() {
    std::stack<int> myStack;
    int number;
    std::cout << "数値を入力してください(-1で終了): ";
    while (true) {
        std::cin >> number;
        if (number == -1) break; // -1が入力されたら終了
        myStack.push(number);
    }
    std::cout << "逆順表示: " << std::endl;
    while (!myStack.empty()) {
        std::cout << myStack.top() << std::endl; // 逆順で表示
        myStack.pop();
    }
    return 0;
}
数値を入力してください(-1で終了): 1
数値を入力してください(-1で終了): 2
数値を入力してください(-1で終了): 3
数値を入力してください(-1で終了): -1
逆順表示: 
3
2
1

括弧のバランスチェック

スタックを使用して、括弧のバランスをチェックするプログラムです。

入力された文字列に含まれる括弧が正しく閉じられているかを確認します。

#include <stack>
#include <iostream>
#include <string>
bool isBalanced(const std::string& str) {
    std::stack<char> myStack;
    for (char ch : str) {
        if (ch == '(') {
            myStack.push(ch); // 開き括弧を追加
        } else if (ch == ')') {
            if (myStack.empty()) {
                return false; // 閉じ括弧が多い
            }
            myStack.pop(); // 開き括弧を削除
        }
    }
    return myStack.empty(); // スタックが空ならバランスが取れている
}
int main() {
    std::string input;
    std::cout << "括弧のバランスをチェックする文字列を入力してください: ";
    std::cin >> input;
    if (isBalanced(input)) {
        std::cout << "括弧はバランスが取れています。" << std::endl;
    } else {
        std::cout << "括弧はバランスが取れていません。" << std::endl;
    }
    return 0;
}
括弧のバランスをチェックする文字列を入力してください: (())
括弧はバランスが取れています。

std::stackの内部実装

デフォルトのコンテナ(std::deque)

std::stackは、デフォルトでstd::dequeを内部コンテナとして使用します。

std::dequeは、両端からの要素の追加や削除が効率的に行えるデータ構造です。

このため、std::stackは、要素の追加pushや削除popをO(1)の時間で実行できます。

以下は、std::stackstd::dequeを使用していることを示す簡単な例です。

#include <stack>
#include <deque>
#include <iostream>
int main() {
    std::stack<int, std::deque<int>> myStack; // std::dequeを使用
    myStack.push(1);
    myStack.push(2);
    myStack.push(3);
    while (!myStack.empty()) {
        std::cout << myStack.top() << std::endl; // 3, 2, 1
        myStack.pop();
    }
    return 0;
}
3
2
1

他のコンテナを使用する方法(std::vector, std::list)

std::stackは、他のコンテナ(std::vectorstd::listなど)を使用することもできます。

これにより、特定の要件に応じてスタックの動作をカスタマイズできます。

以下は、std::vectorを使用したstd::stackの例です。

#include <stack>
#include <vector>
#include <iostream>
int main() {
    std::stack<int, std::vector<int>> myStack; // std::vectorを使用
    myStack.push(10);
    myStack.push(20);
    myStack.push(30);
    while (!myStack.empty()) {
        std::cout << myStack.top() << std::endl; // 30, 20, 10
        myStack.pop();
    }
    return 0;
}
30
20
10

また、std::listを使用する場合の例は以下の通りです。

#include <stack>
#include <list>
#include <iostream>
int main() {
    std::stack<int, std::list<int>> myStack; // std::listを使用
    myStack.push(5);
    myStack.push(15);
    myStack.push(25);
    while (!myStack.empty()) {
        std::cout << myStack.top() << std::endl; // 25, 15, 5
        myStack.pop();
    }
    return 0;
}
25
15
5

このように、std::stackは異なるコンテナを使用することで、さまざまな用途に応じた柔軟なデータ管理が可能です。

選択するコンテナによって、スタックの性能や特性が変わるため、使用するシナリオに応じて適切なコンテナを選ぶことが重要です。

std::stackの応用例

深さ優先探索(DFS)での利用

深さ優先探索(DFS)は、グラフや木構造を探索するアルゴリズムの一つで、スタックを使用して実装することができます。

以下の例では、隣接リストを用いたグラフのDFSを示します。

#include <iostream>
#include <vector>
#include <stack>
void DFS(int start, const std::vector<std::vector<int>>& graph) {
    std::stack<int> myStack;
    std::vector<bool> visited(graph.size(), false);
    myStack.push(start);
    while (!myStack.empty()) {
        int node = myStack.top();
        myStack.pop();
        if (!visited[node]) {
            visited[node] = true;
            std::cout << "訪問: " << node << std::endl;
            for (int neighbor : graph[node]) {
                if (!visited[neighbor]) {
                    myStack.push(neighbor);
                }
            }
        }
    }
}
int main() {
    std::vector<std::vector<int>> graph = {
        {1, 2},    // 0の隣接ノード
        {0, 3, 4}, // 1の隣接ノード
        {0},       // 2の隣接ノード
        {1},       // 3の隣接ノード
        {1}        // 4の隣接ノード
    };
    std::cout << "深さ優先探索の結果:" << std::endl;
    DFS(0, graph); // 0から探索開始
    return 0;
}
深さ優先探索の結果:
訪問: 0
訪問: 2
訪問: 1
訪問: 4
訪問: 3

関数呼び出しのシミュレーション

スタックは、関数呼び出しの管理にも利用されます。

プログラムが関数を呼び出すと、スタックにその情報が保存され、関数が終了するとスタックから取り出されます。

以下は、関数呼び出しをシミュレートする簡単な例です。

#include <iostream>
#include <stack>
void simulateFunctionCall(int n) {
    std::stack<int> callStack;
    for (int i = 1; i <= n; ++i) {
        callStack.push(i); // 関数呼び出しをシミュレート
        std::cout << "関数 " << i << " を呼び出しました。" << std::endl;
    }
    while (!callStack.empty()) {
        std::cout << "関数 " << callStack.top() << " を終了しました。" << std::endl;
        callStack.pop(); // 関数の終了をシミュレート
    }
}
int main() {
    simulateFunctionCall(3); // 3つの関数呼び出しをシミュレート
    return 0;
}
関数 1 を呼び出しました。
関数 2 を呼び出しました。
関数 3 を呼び出しました。
関数 3 を終了しました。
関数 2 を終了しました。
関数 1 を終了しました。

Undo機能の実装

スタックは、アプリケーションの Undo 機能を実装する際にも非常に便利です。

ユーザーが行った操作をスタックに保存し、Undo操作が要求されたときにその操作を取り消すことができます。

以下は、簡単なUndo機能の実装例です。

#include <iostream>
#include <stack>
#include <string>
class TextEditor {
private:
    std::stack<std::string> history; // 操作履歴を保存するスタック
    std::string currentText;
public:
    void type(const std::string& text) {
        history.push(currentText); // 現在のテキストを保存
        currentText += text; // 新しいテキストを追加
        std::cout << "現在のテキスト: " << currentText << std::endl;
    }
    void undo() {
        if (!history.empty()) {
            currentText = history.top(); // 最後の操作を取り消す
            history.pop();
            std::cout << "Undo: 現在のテキスト: " << currentText << std::endl;
        } else {
            std::cout << "Undoできる操作がありません。" << std::endl;
        }
    }
};
int main() {
    TextEditor editor;
    editor.type("こんにちは");
    editor.type("、世界!");
    editor.undo(); // 最後の操作を取り消す
    editor.undo(); // 最後の操作を取り消す
    editor.undo(); // Undoできる操作がありません。
    return 0;
}
現在のテキスト: こんにちは
現在のテキスト: こんにちは、世界!
Undo: 現在のテキスト: こんにちは
Undo: 現在のテキスト: 
Undoできる操作がありません。

このように、std::stackはさまざまな場面で活用され、効率的なデータ管理を実現します。

よくある質問

std::stackとstd::queueの違いは?

std::stackstd::queueはどちらもC++の標準ライブラリに含まれるコンテナアダプタですが、データの管理方法が異なります。

std::stackはLIFO(Last In, First Out)方式で、最後に追加した要素が最初に取り出されます。

一方、std::queueはFIFO(First In, First Out)方式で、最初に追加した要素が最初に取り出されます。

このため、用途に応じて適切なデータ構造を選択することが重要です。

std::stackのメモリ効率は?

std::stackのメモリ効率は、内部で使用するコンテナによって異なります。

デフォルトではstd::dequeが使用され、要素の追加や削除が効率的に行えます。

std::vectorを使用する場合、メモリの再割り当てが発生することがあるため、スタックのサイズが大きくなるとメモリ効率が低下する可能性があります。

使用するコンテナの特性を理解し、適切に選択することが重要です。

std::stackを使う際の注意点は?

std::stackを使用する際の注意点として、以下の点が挙げられます:

  • 空のスタックからのpopやtop: スタックが空の状態でpoptopを呼び出すと、未定義の動作が発生します。

必ずemptyメソッドで確認してから操作を行うようにしましょう。

  • コンテナの選択: 使用するコンテナによって性能が異なるため、用途に応じて適切なコンテナを選ぶことが重要です。
  • スコープの管理: スタックに保存したデータがスコープを超えて使用される場合、データの整合性に注意が必要です。

まとめ

この記事では、C++のstd::stackについて詳しく解説しました。

スタックの基本操作から応用例、よくある質問までを通じて、スタックの特性や使用方法を理解することができたでしょう。

今後は、実際のプログラミングにおいてstd::stackを活用し、効率的なデータ管理を実現してみてください。

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