見出し画像

データ構造-2:スタックとキュー

この記事は、chatGPT o1が書いています。
データ構造の2回目です。
リンク先のnotebookで動作確認できます。ぜひ、動かしてみてください。
※イラストもchatGPTに描いてもらったけど、スタックとキューを表現できているかどうかは微妙。


スタックとキューを徹底解説!

初心者でも分かる Python の基本データ構造
プログラミングの中でよく出てくる「スタック(Stack)」と「キュー(Queue)」。実はこれらは、データの入れ方・出し方を決めるときに使う、とても大事な考え方なんです。

  • スタック:後入れ先出し (LIFO)

    • 最後に入れたものを、最初に取り出す

  • キュー:先入れ先出し (FIFO)

    • 最初に入れたものを、最初に取り出す

「いきなり LIFO とか FIFO とか何?」と思った方も、心配しなくて大丈夫! 身近な例と一緒にコードを見ながら、1つ1つ理解していきましょう。


1. スタック(Stack)とは

1.1 スタックの身近な例

イメージとしては、「積み重ねられたお皿」を想像してください。上に新しいお皿を重ねていき、取り出すときも上にあるお皿から取るはずですよね。

  • 後入れ先出し(LIFO)

    • 新しく追加したものが「一番上」に積まれ、取り出すときは「一番上」から先に出す。

1.2 Python でスタックを実装してみよう

実際に Python でクラスとして作ってみましょう。以下のコードでは、リストの append() と pop() を使うと「末尾に追加」「末尾から削除」が簡単にできます。これがスタックの「上に積む」「上から取り出す」動きとピッタリ一致するのです。

class Stack:
    def __init__(self, max_size=100000):
        """
        スタックを初期化する。
        max_size: スタックの最大サイズを指定。
        """
        self.max_size = max_size
        self.stack = []  # スタックのデータを格納するリスト

    def is_empty(self):
        """ スタックが空かどうかを判定する """
        return len(self.stack) == 0

    def is_full(self):
        """ スタックが満杯かどうかを判定する """
        return len(self.stack) == self.max_size

    def push(self, x):
        """ スタックに要素を追加する """
        if self.is_full():
            print("error: stack is full.")
            return
        self.stack.append(x)  # リストの末尾に要素を追加

    def pop(self):
        """ スタックの最上位の要素を取り出す """
        if self.is_empty():
            print("error: stack is empty.")
            return None
        return self.stack.pop()  # リストの末尾の要素を取り出す

# スタックの動作確認
if __name__ == "__main__":
    stack = Stack()  # スタックを作成

    stack.push(3)  # スタックに3を追加 {}→{3}
    stack.push(5)  # スタックに5を追加 {3}→{3, 5}
    stack.push(7)  # スタックに7を追加 {3, 5}→{3, 5, 7}

    print(stack.pop())  # 7 を取り出す → スタックは {3, 5} に
    print(stack.pop())  # 5 を取り出す → スタックは {3} に

    stack.push(9)       # スタックに9を追加 → {3, 9}

1.3 コードのポイント

  1. push()

    • スタックがいっぱい (is_full()) ならエラー表示

    • まだ余裕があれば list.append(x) で末尾に追加

  2. pop()

    • スタックが空 (is_empty()) ならエラー表示

    • そうでなければ、末尾 (list.pop()) の要素を取り出す

お皿を重ねるみたいに、「あとから積んだものが先に出てくる」動作を実現しています。


2. キュー(Queue)とは

2.1 キューの身近な例

例えるなら「行列に並ぶ人の列」です。列に並んだ順で処理されていきます。

  • 先入れ先出し(FIFO)

    • 最初に入れた要素が、先に取り出される。

たとえば、スーパーのレジ待ちでは先に並んでいた人が先に会計を済ませますよね。後ろから割り込むことは(普通は)ありません。

2.2 Python でキューを実装してみよう

2.2.1 collections.deque を使った実装

Python には、キューの操作を効率的に行える「deque(デック)」という便利なデータ型が標準で用意されています。先頭から取り出す操作が非常に高速で、キューを作るなら基本的にはこちらを使うのがおすすめです。

from collections import deque

class Queue:
    def __init__(self, max_size=100000):
        """ キューを初期化する """
        self.max_size = max_size
        self.queue = deque()

    def is_empty(self):
        """ キューが空かどうかを判定する """
        return len(self.queue) == 0

    def is_full(self):
        """ キューが満杯かどうかを判定する """
        return len(self.queue) >= self.max_size

    def enqueue(self, x):
        """ キューに要素を追加する """
        if self.is_full():
            print("error: queue is full.")
            return
        self.queue.append(x)  # 末尾に追加

    def dequeue(self):
        """ キューから要素を取り出す """
        if self.is_empty():
            print("error: queue is empty.")
            return -1
        return self.queue.popleft()  # 先頭から取り出す

# 使用例
q = Queue()

q.enqueue(3)  # キューに3を追加
q.enqueue(5)  # キューに5を追加
q.enqueue(7)  # キューに7を追加

print(q.dequeue())  # 3を出力(先に入れた3が先に出る)
print(q.dequeue())  # 5を出力

q.enqueue(9)        # キューに9を追加

ポイント

  • enqueue のときは append(x) → 末尾に追加

  • dequeue のときは popleft() → 先頭から取り出し

  • キューが空かどうかを is_empty()、満杯かどうかを is_full() でチェック

2.2.2 リストを使った実装

「自分で 0 番目から取り出すコードを書いてみたい!」という学習目的であれば、リストを使ったキュー実装も勉強になります。ただし先頭 (index=0) から取り出す場合、その度に要素を詰め直すため、要素数が多いと処理速度が落ちる点は注意しましょう。

class Queue:
    def __init__(self, max_size=100000):
        """ キューを初期化する """
        self.queue = []  # リストを使ってキューを管理
        self.max_size = max_size

    def is_empty(self):
        """ キューが空かどうかを判定する """
        return len(self.queue) == 0

    def is_full(self):
        """ キューが満杯かどうかを判定する """
        return len(self.queue) >= self.max_size

    def enqueue(self, x):
        """ キューに要素を追加する """
        if self.is_full():
            print("error: queue is full.")
            return
        self.queue.append(x)  # リストの末尾に追加

    def dequeue(self):
        """ キューから要素を取り出す """
        if self.is_empty():
            print("error: queue is empty.")
            return -1
        return self.queue.pop(0)  # リストの先頭を取り出す

# 使用例
q = Queue()

q.enqueue(3)  # キューに3を追加
q.enqueue(5)  # キューに5を追加
q.enqueue(7)  # キューに7を追加

print(q.dequeue())  # 3を出力
print(q.dequeue())  # 5を出力

q.enqueue(9)        # キューに9を追加

3. スタックとキューの違いを整理しよう

  • スタック(Stack)

    • 後入れ先出し (LIFO)

    • 例:

      • 皿を積み重ねる

      • 戻るボタンの履歴(一番新しいページから戻る)

      • 関数コールスタック(プログラムが関数を呼び出すときに、呼び出し元の情報を上に積む)

  • キュー(Queue)

    • 先入れ先出し (FIFO)

    • 例:

      • レジ待ちの行列

      • プリンターの印刷待ちタスク

      • メッセージやイベントを順番に処理するシステム

2 つのデータ構造は、仕事やプログラミングのあらゆる場面で頻繁に登場します。どちらが適しているかは、「最後に入れた要素を先に処理するのか? それとも 最初に入れた要素を先に処理するのか?」の違いで判断するとよいでしょう。


4. まとめ

  • スタックキューは、データを「どんな順序で」取り出すかを管理する仕組み。

  • スタック (LIFO): 最後に入れた要素が最初に出てくる

  • キュー (FIFO): 最初に入れた要素が最初に出てくる

  • Python での実装は、スタックはリストの append() + pop()、キューは主に collections.deque を使うと楽ちん。

  • リストでキューを実装する方法も知っておくと「内部ではこう動いているのか!」という学習になる。

スタックとキューの考え方は、これから先、アルゴリズムやデータ構造をもっと勉強する際の「土台」となります。どちらも使いどころが多いので、ぜひこの機会にしっかり押さえておきましょう!


5. 次に挑戦してみるなら…

  • 応用のデータ構造

    • リスト連結リスト二分木 (Binary Tree)グラフ (Graph) など、より複雑なデータ構造にステップアップ

  • アルゴリズム

    • 幅優先探索 (BFS) や深さ優先探索 (DFS) で、キューやスタックがどのように使われるか学んでみる

  • 実践例

    • ゲームプログラミングでイベント処理の順序管理

    • Web アプリでのユーザ操作履歴管理

ちょっとずつステップアップしていくと、プログラミングがもっと面白く、もっと役立つものになっていきますよ。まずは今回のコードをコピーして、実際に手元の Python で動かしてみてください。学びは「動かしてナンボ」です!
もしエラーが出たり、分からないことがあったら、気軽に調べたり、誰かに質問してみましょう。それでは、快適なスタック&キューライフをお楽しみください!


最後まで読んでくださり、ありがとうございました!
これを機に、スタックとキューの理解をしっかり固めていただければ幸いです。今後のプログラミング学習にきっと役立ちますので、ぜひ活用してみてくださいね。


補足:print文で動作過程を可視化

スタック(Stack)の実装例

class Stack:
    def __init__(self, max_size=100000):
        """
        リストを使ってスタックを初期化する
        """
        self.max_size = max_size
        self.stack = []

    def is_empty(self):
        """スタックが空かどうかを判定"""
        return len(self.stack) == 0

    def is_full(self):
        """スタックが満杯かどうかを判定"""
        return len(self.stack) >= self.max_size

    def push(self, x):
        """スタックに要素を追加する (後入れ先出し: LIFO)"""
        if self.is_full():
            print("error: stack is full.")
            return
        self.stack.append(x)
        print(f"[PUSH] {x} をスタックに追加 → 現在のスタック: {self.stack}")

    def pop(self):
        """スタックの最上位要素を取り出す"""
        if self.is_empty():
            print("error: stack is empty.")
            return None
        popped_item = self.stack.pop()
        print(f"[POP] {popped_item} を取り出した → 現在のスタック: {self.stack}")
        return popped_item

# 動作確認用コード
if __name__ == "__main__":
    stack = Stack()  # スタックのインスタンスを作成

    stack.push(3)  # スタックに3を追加
    stack.push(5)  # スタックに5を追加
    stack.push(7)  # スタックに7を追加

    stack.pop()    # 一番上(最後に入れた7)を取り出す
    stack.pop()    # 次に上にある(最後に入れた5)を取り出す

    stack.push(9)  # スタックに9を追加

解説(最小限)

  • push() で要素を追加するたびに、print() で「何を追加したのか」「現在のスタックがどう変化したか」が分かるように表示しています。

  • pop() で要素を取り出すたびに、取り出した後のスタックの状態を表示しています。

  • スタックは「後入れ先出し(LIFO)」なので、最後に追加したものを先に取り出します。


キュー(Queue)の実装例

class Queue:
    def __init__(self, max_size=100000):
        """
        リストを使ってキューを初期化する
        """
        self.queue = []
        self.max_size = max_size

    def is_empty(self):
        """キューが空かどうかを判定"""
        return len(self.queue) == 0

    def is_full(self):
        """キューが満杯かどうかを判定"""
        return len(self.queue) >= self.max_size

    def enqueue(self, x):
        """キューに要素を追加する (先入れ先出し: FIFO)"""
        if self.is_full():
            print("error: queue is full.")
            return
        self.queue.append(x)
        print(f"[ENQUEUE] {x} をキューに追加 → 現在のキュー: {self.queue}")

    def dequeue(self):
        """キューの先頭から要素を取り出す"""
        if self.is_empty():
            print("error: queue is empty.")
            return None
        front_item = self.queue.pop(0)
        print(f"[DEQUEUE] {front_item} を取り出した → 現在のキュー: {self.queue}")
        return front_item

# 動作確認用コード
if __name__ == "__main__":
    q = Queue()

    q.enqueue(3)  # キューに3を追加
    q.enqueue(5)  # キューに5を追加
    q.enqueue(7)  # キューに7を追加

    q.dequeue()   # 先に入れた3が先に取り出される
    q.dequeue()   # 次に先頭にあった5を取り出す

    q.enqueue(9)  # キューに9を追加

解説(最小限)

  • enqueue() で要素を末尾に追加するとき、キューの中身を print() で確認できるようにしています。

  • dequeue() で先頭の要素を取り出したら、取り出した要素とキューの新しい状態を表示するので、「先に入れたものが先に出る(FIFO)」様子がよく分かります。


まとめ

  • スタック後入れ先出し (LIFO)キュー先入れ先出し (FIFO) の構造をとります。

  • 上記のコードでは、操作するたびに print() で内部状態を表示するため、「何が起こっているのか」を一目瞭然で確認できます。

  • 初学者の方は、このコードを実行しながら、どのタイミングでどの要素が追加され、どの要素が取り出されるのかをぜひ追いかけてみてください。きっと理解が深まるはずです!

いいなと思ったら応援しよう!