std::stackは、C++標準ライブラリで提供されるLIFO(Last In, First Out)構造を持つコンテナアダプタです。
このクラスは、デフォルトでstd::deque
を基に実装されていますが、std::vector
やstd::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::vector
やstd::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::stack
がstd::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::vector
やstd::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
はさまざまな場面で活用され、効率的なデータ管理を実現します。
よくある質問
まとめ
この記事では、C++のstd::stack
について詳しく解説しました。
スタックの基本操作から応用例、よくある質問までを通じて、スタックの特性や使用方法を理解することができたでしょう。
今後は、実際のプログラミングにおいてstd::stack
を活用し、効率的なデータ管理を実現してみてください。