CodeGym /コース /JAVA 25 SELF /Queue, Deque, Stack: キューとスタックの操作

Queue, Deque, Stack: キューとスタックの操作

JAVA 25 SELF
レベル 27, レッスン 2
使用可能

1. Queue インターフェイス: 古典的なキュー(FIFO)

プログラミングでも日常と同じく、単に要素の集合を保持するだけでなく、処理順序を管理することが重要な場面があります。スーパーマーケットの列を想像してください。先頭に近い人が先に対応されます。これは FIFO(先入れ先出し)の原理です。一方、食器棚の皿の山は LIFO(後入れ先出し)の原理で動きます。Java ではこれらの原理に対応するコレクションとして、キューの Queue、両端キューの Deque、スタックの Stack があります。

主な用途:

  • 到着順にタスクを処理(タスクキュー、印刷ジョブ、イベント処理)。
  • 操作の取り消し(undo/redo)— スタック。
  • 式のパース、木やグラフの走査(幅優先/深さ優先)など。

キューとは?

キュー(Queue)は FIFO の原理に基づくコレクションです。要素は末尾に追加され、先頭から取り出されます。店の列と同じで、早く来た人から先に処理されます。

Queue インターフェイスの主要メソッド

メソッド 動作 戻り値
offer(e)
末尾に要素を追加(例外を投げない) true/false
add(e)
末尾に要素を追加(例外を投げる) true/Exception
poll()
先頭要素を削除して返す 要素/null
remove()
先頭要素を削除して返す 要素/Exception
peek()
先頭要素を削除せずに返す 要素/null
element()
先頭要素を削除せずに返す 要素/Exception

似た挙動のメソッドがペアになっています。違いは「やさしさ」です。offer、poll、peek は失敗時に特別な値(通常は null や false)を返し、例外を投げません。一方、add、remove、element は境界状況(空のキュー、場合によっては満杯)で例外を投げます。

例: タスクキュー

import java.util.*;

public class QueueDemo {
    public static void main(String[] args) {
        Queue<String> tasks = new LinkedList<>();
        tasks.offer("歯を磨く");
        tasks.offer("体操をする");
        tasks.offer("コーヒーを飲む");

        while (!tasks.isEmpty()) {
            String task = tasks.poll(); // キューの先頭からタスクを取り出す
            System.out.println("実行中: " + task);
        }
    }
}

出力:

実行中: 歯を磨く
実行中: 体操をする
実行中: コーヒーを飲む

なぜ LinkedList がよく使われるのか

インターフェイスは契約であり、実装はいくつかあります。よく使われるのは LinkedList で、両端への追加/削除が高速だからです。ただし優れた代替として ArrayDeque があります。特化型としては PriorityQueue(優先度付きキュー)もあり、これは後述します。

2. Deque インターフェイス: 両端キュー

Deque(Double Ended Queue、デック)は、両端(先頭と末尾)から要素を追加・削除できるキューです。両側にドアがあるバスのように、どちら側からでも乗り降りできます。

Deque = 万能選手: 通常のキュー(FIFO)としても、スタック(LIFO)としても、ハイブリッドとしても使えます。

Deque インターフェイスの主要メソッド

メソッド 動作 使用例
addFirst(e)
先頭に追加
ochered.addFirst("A")
addLast(e)
末尾に追加
ochered.addLast("B")
removeFirst()
先頭から削除して返す
ochered.removeFirst()
removeLast()
末尾から削除して返す
ochered.removeLast()
peekFirst()
先頭を参照(削除しない)
ochered.peekFirst()
peekLast()
末尾を参照(削除しない)
ochered.peekLast()
offerFirst(e)
先頭に追加(例外を投げない)
ochered.offerFirst("C")
offerLast(e)
末尾に追加(例外を投げない)
ochered.offerLast("D")

例: Deque をキューとして/スタックとして使う

import java.util.*;

public class DequeDemo {
    public static void main(String[] args) {
        Deque<String> deque = new ArrayDeque<>();

        // キュー(FIFO)として使用
        deque.offerLast("A");
        deque.offerLast("B");
        deque.offerLast("C");
        System.out.println("キュー (FIFO):");
        while (!deque.isEmpty()) {
            System.out.println(deque.pollFirst());
        }

        // スタック(LIFO)として使用
        deque.offerLast("1");
        deque.offerLast("2");
        deque.offerLast("3");
        System.out.println("スタック (LIFO):");
        while (!deque.isEmpty()) {
            System.out.println(deque.pollLast());
        }
    }
}

出力:

キュー (FIFO):
A
B
C
スタック (LIFO):
3
2
1

なぜ ArrayDeque なのか

ArrayDeque は配列ベースの Deque の高速でコンパクトな実装です。スタックやキュー用途では古い Stack より一般に高速かつ信頼性が高く、固定サイズではありません(メモリによってのみ制限されます)。

3. Stack: スタック — 後入れ先出し(LIFO)

スタックは LIFO の原理に基づくコレクションで、最後に追加した要素を上から取り出します。プログラミングでは、操作履歴(undo)、再帰的な構造(木/グラフ)の走査、式の評価などに役立ちます。

Stack クラス(そしてそれを使うべきでない理由)

Java には Stack クラスがありますが、これは古い Vector を継承しています。現在では新しいプロジェクトでの使用は推奨されません。代わりに Deque/ArrayDeque を使いましょう。

スタック操作(Deque)

メソッド 動作
push(e)
要素をスタックの頂上に積む
pop()
頂上の要素を取り出して返す
peek()
頂上の要素を参照

注意: Deque ではこれらの操作は addFirst/removeFirst/peekFirst に対応しますが、互換性のために「スタック風」の名前 push/pop/peek も用意されています。

例: ArrayDeque を使ったスタック

import java.util.*;

public class StackDemo {
    public static void main(String[] args) {
        Deque<String> stack = new ArrayDeque<>();
        stack.push("一番目");
        stack.push("二番目");
        stack.push("三番目");

        while (!stack.isEmpty()) {
            System.out.println(stack.pop());
        }
    }
}

出力:

三番目
二番目
一番目

なぜ Stack は勧められないのか

  • Stack は同期化されており(遅い)、古い Vector を継承しています。
  • ArrayDeque はたいてい高速でコンパクトで、スレッドをブロックしません。
  • 新しいコードで Stack を見かけたら、通常は ArrayDeque への置き換え候補です。

4. 使い分けの指針

構造 原理 用途 代表的な実装
Queue FIFO タスクキュー、イベント処理、印刷、顧客の待ち行列
LinkedList, ArrayDeque, PriorityQueue
Stack LIFO Undo/redo、再帰、パーサー、木の走査
ArrayDeque
Deque FIFO/LIFO バッファ、回文判定、両端キュー、汎用タスク
ArrayDeque, LinkedList

身近な例:

  • 銀行の行列 — Queue。
  • エディタの操作履歴 — Stack。
  • 両側からの乗降があるバスの列 — Deque。

5. コード例: ミニアプリでキューとスタックを実装

例1: 印刷ジョブのキュー

import java.util.*;

public class PrintQueueApp {
    public static void main(String[] args) {
        Queue<String> printQueue = new ArrayDeque<>();
        printQueue.offer("Document1.pdf");
        printQueue.offer("Document2.docx");
        printQueue.offer("Document3.xls");

        while (!printQueue.isEmpty()) {
            String doc = printQueue.poll();
            System.out.println("印刷中: " + doc);
        }
    }
}

例2: 操作の取り消しスタック(undo)

import java.util.*;

public class UndoStackApp {
    public static void main(String[] args) {
        Deque<String> undoStack = new ArrayDeque<>();
        undoStack.push("テキストの挿入");
        undoStack.push("色の変更");
        undoStack.push("画像の削除");

        System.out.println("最後の操作: " + undoStack.peek()); // 画像の削除

        while (!undoStack.isEmpty()) {
            System.out.println("取り消し: " + undoStack.pop());
        }
    }
}

例3: 両端キュー

import java.util.*;

public class DequeApp {
    public static void main(String[] args) {
        Deque<String> deque = new ArrayDeque<>();
        deque.addFirst("A");
        deque.addLast("B");
        deque.addFirst("C");
        deque.addLast("D");

        System.out.println("先頭から取り出す: " + deque.removeFirst()); // C
        System.out.println("末尾から取り出す: " + deque.removeLast());   // D
        System.out.println("残り: " + deque); // [A, B]
    }
}

6. 実装上の特徴と注意点

  • ArrayDeque は、多くの用途で最速かつ汎用的なキュー/スタック実装です。
  • LinkedList も Deque を実装しています。役立つ場合もありますが、短いキューでは一般に ArrayDeque より遅いことが多いです。
  • PriorityQueue は優先度付きキューで、順序は優先度(自然順序または Comparator)で決まります。通常の FIFO キューではありません。
  • Stack は古く、同期化されています。新規プロジェクトでは ArrayDeque を推奨します。
  • Deque は先頭と末尾の両方を扱えるため、キューとスタックのシナリオを単一の抽象でカバーできて便利です。

7. キューとスタックでよくあるミス

よくあるミス1: Deque の代わりに Stack を使う。
初心者は名前だけを見て Stack クラスを選びがちです。現代のプロジェクトでは代わりに ArrayDeque(または LinkedList)を用い、push/pop を使います。

よくあるミス2: データ構造の原理に反する使い方。
キューやスタックの要素をインデックスで参照しようとする(例: queue.get(0) や stack.get(0))。これは NG です — キュー/スタック用のメソッド(peek、poll、pop など)を使いましょう。

よくあるミス3: remove/element/add を無検査で使う。
remove、element、add は、空/満杯の状態で例外を投げることがあります。より安全なのは、特別な値を返す「やさしい」 offer、poll、peek を使うことです。

よくあるミス4: ArrayDeque に null 要素を入れる。
ArrayDeque は null を許可しません。null を追加しようとすると NullPointerException になります。

よくあるミス5: PriorityQueue を通常のキューとして使う。
PriorityQueue では順序が優先度(自然順序または Comparator)で決まります。厳密な FIFO が必要なら、ArrayDeque や LinkedList を使いましょう。

よくあるミス6: for-each で走査中にキュー/スタックを変更する。
for-each 中にコレクションから要素を削除すると、ConcurrentModificationException を引き起こすことがあります。削除にはイテレータを使うか、例のようにループ内で poll/pop を使いましょう。

1
タスク
JAVA 25 SELF, レベル 27, レッスン 2
ロック未解除
システム内の次のタスクを確認する 💼
システム内の次のタスクを確認する 💼
1
タスク
JAVA 25 SELF, レベル 27, レッスン 2
ロック未解除
プリンターの動作と操作履歴のシミュレーション 🖨️🔄
プリンターの動作と操作履歴のシミュレーション 🖨️🔄
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION