基本的なデータ構造を理解しよう!🧱
こんにちは!C言語学習の旅、Step 7へようこそ!ここでは、プログラムでデータを効率的に扱うための基本的な「型」とも言える「データ構造」の中から、特に重要なスタック、キュー、そしてリスト構造について学びます。これらは、より複雑なアルゴリズムやプログラムを構築するための基礎となります。💪
これまでのステップで学んだポインタや構造体、動的メモリ確保の知識がここで活きてきます。準備はいいですか?さっそく見ていきましょう!
スタック (Stack) 🥞
スタックは、LIFO (Last-In, First-Out)、つまり「後入れ先出し」の原則に従うデータ構造です。皿洗いで積み重ねたお皿をイメージしてください。一番最後に置いたお皿を一番最初に取り出しますよね?それと同じです。
主な操作
- Push (プッシュ): スタックの一番上に新しいデータを追加します。
- Pop (ポップ): スタックの一番上のデータを取り出します(取り出したデータはスタックから削除されます)。
C言語での実装
スタックは配列や連結リストを使って実装できます。ここでは、連結リストを使ったスタックのイメージを見てみましょう。新しいデータ(ノード)を追加するときはリストの先頭に追加し、取り出すときも先頭から取り出します。これはStep 6で学んだ動的メモリ確保 (malloc
, free
) や Step 4の構造体 (struct
)、Step 3のポインタの知識を活用します。
用途例
- 💻 関数の呼び出しと戻りアドレスの管理(コールスタック)
- ⏪ テキストエディタなどの「元に戻す」(Undo)機能
- 🧮 数式の構文解析や評価(逆ポーランド記法など)
- 🌲 深さ優先探索 (DFS: Depth First Search) アルゴリズム
- 🔙 ウェブブラウザの「戻る」ボタンの履歴
キュー (Queue) 🚶♂️🚶♀️🚶
キューは、FIFO (First-In, First-Out)、つまり「先入れ先出し」の原則に従うデータ構造です。スーパーのレジ待ちの行列を思い浮かべてください。最初に列に並んだ人が、最初に会計を済ませますよね?それと同じ仕組みです。
主な操作
- Enqueue (エンキュー): キューの末尾 (rear) に新しいデータを追加します。
- Dequeue (デキュー): キューの先頭 (front) からデータを取り出します(取り出したデータはキューから削除されます)。
C言語での実装
キューも配列や連結リストで実装できます。連結リストで実装する場合、効率的に末尾への追加と先頭からの削除を行うために、リストの先頭 (front) と末尾 (rear) の両方を指すポインタを持つことが一般的です。
用途例
- 📠 プリンターへの印刷ジョブの待ち行列
- ⌨️ キーボード入力やマウスイベントなどの処理待ち
- 🌐 ネットワーク通信におけるパケットの順次処理
- 🗺️ 幅優先探索 (BFS: Breadth First Search) アルゴリズム(グラフやツリーの探索)
- ⏳ OSによるタスクスケジューリング(実行待ちタスクの管理)
リスト構造 (Linked List) 🔗
リスト構造(連結リスト)は、データを格納する「ノード」と呼ばれる要素を、ポインタを使って鎖のようにつなげたデータ構造です。配列 (Step 2で学習) とは異なり、メモリ上に要素が連続して配置されている必要がありません。そのため、データの追加や削除が柔軟かつ効率的に行えるという大きなメリットがあります。
基本的な概念
- ノード (Node): データ本体と、次のノードを指すポインタ(
next
)を持ちます。これがリストの構成要素です。 - ポインタ (Pointer): ノードとノードをつなぐ「鎖」の役割を果たします。リストの先頭を指すポインタ(しばしば
head
と呼ばれる)が、リスト全体へのアクセス起点となります。
主な種類
リスト構造にはいくつかのバリエーションがあります。
- 単方向リスト (Singly Linked List): 各ノードが次のノードへのポインタ (
next
) のみを持つ、一方向のリスト。今回主に説明するのはこれです。 - 双方向リスト (Doubly Linked List): 各ノードが次 (
next
) と前 (prev
) の両方のノードへのポインタを持つリスト。逆方向への走査も容易ですが、ポインタ管理が少し複雑になります。 - 循環リスト (Circular Linked List): 末尾のノードが先頭のノードを指す、ループ状になったリスト。リストのどこからでも走査を開始できるなどの利点があります(単方向・双方向どちらもあり)。
C言語での実装(単方向リスト)
リスト構造の実装には、ノードを表す自己参照構造体(自分自身の型へのポインタを持つ構造体)と、動的メモリ確保 (malloc
, free
) が不可欠です。
配列との比較
リスト構造は配列とよく比較されます。それぞれの利点と欠点を理解し、状況に応じて使い分けることが重要です。
特徴 | 配列 (Array) | 連結リスト (Linked List) |
---|---|---|
💾メモリ配置 | 連続したメモリ領域が必要 | メモリ上に散らばっていても良い(ポインタで接続) |
📏サイズ | 静的(コンパイル時)または動的(realloc 等で変更可能だがコストがかかる) | 動的(実行時にノードを追加・削除して容易にサイズ変更) |
🔍要素アクセス | 高速 (O(1) ) – インデックスで直接アクセス | 低速 (O(n) ) – 先頭から順にたどる必要あり |
➕➖要素の挿入/削除 | 低速 (O(n) ) – 特に途中への挿入/削除は後続要素のシフトが必要 | 高速 (O(1) ) – 対象ノード(またはその前)のポインタが分かれば、ポインタ繋ぎ替えのみ ※ノード探索時間は別途必要 |
💰メモリ効率 | データのみを格納するため効率が良い | データに加え、ポインタ格納分のメモリも必要 |
※ O(1)
は処理時間がデータ量によらず一定、O(n)
はデータ量に比例して処理時間が増加することを意味します(計算量という概念です)。
用途例
- 💾 サイズが事前に予測できない、または頻繁に変動するデータの管理
- 🔄 要素の挿入や削除が頻繁に発生する場面
- 🏗️ スタックやキューなど、他のデータ構造を実装するための基盤として
- 🎵 音楽プレイヤーのプレイリストのように、曲順の変更や追加・削除が容易に必要な場合
- 📝 OSでのメモリ管理(空き領域リストなど)
まとめ ✨
今回は、基本的なデータ構造であるスタック、キュー、リスト構造について、それぞれの概念、主な操作、C言語での実装イメージ、そして用途例を見てきました。
- スタック: LIFO (後入れ先出し)。Push / Pop。関数呼び出しやUndo機能など。
- キュー: FIFO (先入れ先出し)。Enqueue / Dequeue。待ち行列やイベント処理など。
- リスト構造: ノードとポインタで連結。動的なサイズ変更、柔軟な挿入/削除。配列とは異なる特性を持つ。
これらのデータ構造は、単なるデータの入れ物ではなく、特定の操作を効率的に行うための「仕組み」です。プログラムでどのようなデータ操作が必要になるかを考え、最適なデータ構造を選択することが、パフォーマンスの良い、洗練されたコードを書くための第一歩となります。 🧰
次のステップでは、今回学んだデータ構造の知識も活用しながら、簡易的なデータベースの実装や、電卓・ToDoリストといった、より具体的なアプリケーションの作成に挑戦します。お楽しみに!🚀
参考情報
これらのデータ構造について、さらに深く学びたい場合は、以下のキーワードで検索したり、信頼できるC言語の参考書や技術サイトを参照することをおすすめします。
C言語 データ構造 スタック 実装
C言語 キュー 連結リスト FIFO
C言語 連結リスト 挿入 削除 アルゴリズム
データ構造とアルゴリズム C言語 入門
struct ポインタ 自己参照構造体 C言語
malloc free メモリ管理 C言語
コメント