はじめに|データ構造とは?プログラムの中の「整理整頓」
プログラムの世界では、たくさんのデータを決められた形で整理・保存する必要があります。データをどう整理するかを決める仕組みが データ構造(Data Structure) です。
現実でも本棚や冷蔵庫のように、「どこに、どう並べるか?」によって使いやすさが変わります。プログラムでも同じことが言えます。
データ構造はプログラムの基礎であり、アルゴリズムとセットで考えることで性能や使いやすさが大きく変わります。どのデータ構造を選ぶかは、解きたい問題やシステムの特性に合わせることが重要です。
例えば:
- 配列 → 固定数のデータを一瞬で取り出したい場合
- リスト → 順番に並べるが途中の追加・削除が多い場合
- キュー・スタック → 「順番」や「積み重ね」のルールがある場合
- 木構造・ハッシュ → 複雑な階層管理や高速検索が必要な場合
graph TD A[データ構造] --> B[配列(Array)] A --> C[リスト(List)] A --> D[キュー(Queue)] A --> E[スタック(Stack)] A --> F[木構造(Tree)] A --> G[ハッシュ(Hash)]
【データ構造の基本】なぜ学ぶべきなの?
整理整頓がプログラムの「速さ」を決める
データが「どこにあるか」がすぐわかれば、検索や処理は速くなります。 逆に、バラバラに散らかったままだと時間がかかります。
ここで登場する考え方が「計算量(ビッグオー記法)」です。
計算量(ビッグオー記法)とは?
「O(1)」「O(n)」は、プログラムの処理速度の目安を示す表記です。
- O(1):一定時間で終わる処理(データ数に関係ない)
→ 例)配列の3番目を取り出す。すぐ終わる。 - O(n):データ数(n)に比例して時間がかかる処理
→ 例)リストの中から3番目を探す。順番に探すため、データが多いと時間がかかる。
こうした目安を理解することで、システムの処理速度や負荷を予測できるようになります。
アルゴリズムの性能を左右する
「並び替え」「検索」「追加・削除」などの処理速度は、データ構造次第で大きく変わります。
例)
- ハッシュは検索が圧倒的に速い(O(1))
- 木構造は階層があるデータの管理や探索に便利
システム設計の質が上がる
データ構造を知ることで、現実のしくみをそのままプログラムに落とし込むことができ、 システムの設計がわかりやすく、ミスの少ないものになります。
例えば:
- 名簿管理(順番に並んでいるだけでなく、素早く検索したい) → ハッシュ(名前やIDから瞬時に探せる)
- フォルダ構造(階層関係を表現したい) → 木構造(親子関係を再現できる)
- レジの順番待ち(順番通りに処理したい) → キュー(先に来た順に処理する仕組み)
現実世界の「整理整頓」や「ルール」をプログラムでも再現することで、 自然で理解しやすく、効率的なシステム設計につながります。
配列(Array / ArrayList)
使うべき理由
- データ数が決まっている
- ランダムアクセスが必要(O(1))
- 大量データ時のメモリ効率が良い
システム例
✔ テストの点数管理(30人分固定)
✔ ゲームキャラのステータス管理(固定人数)
通常の配列(int[])版
public class NormalArrayExample {
public static void main(String[] args) {
int[] scores = new int[30]; // サイズ固定
for (int i = 0; i < scores.length; i++) {
scores[i] = (int)(Math.random() * 100);
}
System.out.println("3人目の点数: " + scores[2]);
}
}
✅ 超高速ランダムアクセス(O(1))
❌ サイズ変更不可・途中追加削除が苦手
ArrayList版
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
ArrayList<Integer> scores = new ArrayList<>();
for (int i = 0; i < 30; i++) {
scores.add((int)(Math.random() * 100)); // intはIntegerへ自動変換
}
System.out.println("3人目の点数: " + scores.get(2));
}
}
✅ サイズ自動拡張・add()
で追加可能
❌ 内部で配列コピー発生・メモリ効率が悪い
通常配列 vs ArrayList 比較表
特徴 | 通常配列(int[]) | ArrayList<Integer> |
---|---|---|
サイズ | 固定 | 自動拡張(add() 可能) |
型 | プリミティブ型OK(int, double等) | ラッパー型必要(Integer等) |
速度 | 高速(O(1)) | 若干遅い(内部コピーやラップのため) |
メモリ効率 | 良い | 悪い(オートボクシング) |
使いやすさ | 低い(手動でサイズ管理) | 高い(メソッド豊富・サイズ自動管理) |
単方向リスト(Singly Linked List)
使うべき理由
- 途中追加・削除が多い
- 順番通りに処理するだけ
- 前には戻らない
システム例
✔ 音楽プレイリスト(曲の順次再生)
✔ タスク管理(順番に実行)
Java実装(自作)
class SongNode {
String title;
SongNode next;
SongNode(String title) { this.title = title; }
}
✅ 途中追加・削除が高速(O(1))
❌ 前に戻れない・ランダムアクセス不可(O(n))
双方向リスト(Java LinkedList)
使うべき理由
- 前後どちらにも移動したい
- 途中のデータを柔軟に編集
- ランダムアクセスより前後ナビゲーションが重要
システム例
✔ 画像ビューア(「前へ」「次へ」ボタン)
✔ ECサイトの商品スライダー
Java LinkedList 実装
import java.util.LinkedList;
import java.util.ListIterator;
public class ImageViewer {
public static void main(String[] args) {
LinkedList<String> images = new LinkedList<>();
images.add("画像1");
images.add("画像2");
images.add("画像3");
ListIterator<String> it = images.listIterator(1); // 画像2から
System.out.println("表示: " + it.next());
System.out.println("次へ: " + it.next());
System.out.println("前へ: " + it.previous());
}
}
✅ 標準ライブラリで双方向リストが実装済み
✅ ListIterator
で前後移動が楽
循環リスト
使うべき理由
- 最後まで行ったら自動で先頭に戻る
- ループ処理が必要
システム例
✔ ゲームのターン制バトル
✔ OSのCPUスケジューラ(ラウンドロビン)
キュー(Queue)
使うべき理由
- 先着順処理(FIFO)
- 「取り出しは先頭」だけのシンプル構造
システム例
✔ プリンターの印刷ジョブ管理
✔ Webサーバーのリクエスト受付順
Java Queue(LinkedList使用)
import java.util.Queue;
import java.util.LinkedList;
Queue<String> printQueue = new LinkedList<>();
printQueue.add("ジョブ1");
printQueue.add("ジョブ2");
System.out.println(printQueue.poll()); // → ジョブ1
✅ FIFO(先入れ先出し)
✅ poll()
で自然な取り出し
スタック(Stack)
使うべき理由
- 「最後に追加したものから取り出す」(LIFO)
- 戻る・Undo機能が必要
システム例
✔ ブラウザの「戻る」ボタン
✔ エディタのUndo処理
Java Stack
import java.util.Stack;
Stack<String> history = new Stack<>();
history.push("ページ1");
history.push("ページ2");
System.out.println(history.pop()); // → ページ2
✅ 後入れ先出し(LIFO)
木構造(Tree)|例:フォルダ階層構造
システム例
✔ フォルダ・サブフォルダ管理
✔ データベースのインデックス構造(B木)
Java簡易ツリー実装
class FolderNode {
String name;
FolderNode left; // 左のサブフォルダ
FolderNode right; // 右のサブフォルダ
FolderNode(String name) {
this.name = name;
}
}
public class FolderTreeExample {
public static void main(String[] args) {
FolderNode root = new FolderNode("Cドライブ");
root.left = new FolderNode("ドキュメント");
root.right = new FolderNode("画像");
root.left.left = new FolderNode("仕事");
root.left.right = new FolderNode("プライベート");
traverse(root);
}
static void traverse(FolderNode node) {
if (node != null) {
System.out.println("フォルダ: " + node.name);
traverse(node.left);
traverse(node.right);
}
}
}
✅ 親子関係・階層が自然に表現できる
✅ 探索や分類が得意
ハッシュ(HashMap)|例:SNSユーザーID検索
システム例
✔ SNSのユーザー管理(userID→名前検索)
✔ 辞書アプリ(単語→意味)
Java HashMap実装
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
HashMap<String, String> userMap = new HashMap<>();
userMap.put("user1", "山田太郎");
userMap.put("user2", "佐藤花子");
userMap.put("user3", "鈴木一郎");
System.out.println("user2の名前: " + userMap.get("user2"));
}
}
✅ キーから瞬時にデータ取得(O(1))
✅ 大量データの検索・管理が得意
【最終まとめ】データ構造の選び方まとめ
データ構造 | 主な活用例 | 適している理由 | 他の構造では? |
---|---|---|---|
配列 | 点数やキャラクターなど固定数の管理 | データ数が決まっていて、高速にアクセスしたい | リストでは検索が遅くなることがある |
単方向リスト | 音楽プレイリストなど順次処理 | 途中の追加・削除がしやすい | 配列だとズレやすい |
双方向リスト | 画像ビューアやスライダー | 前後の移動が簡単 | 配列だと移動や追加削除が大変 |
キュー | 印刷ジョブや順番待ち | 順番通りの処理(FIFO)に最適 | スタックだと逆順になる |
スタック | ブラウザ履歴やUndo機能 | 後から積んだものを先に取り出せる(LIFO) | キューでは順序が逆になる |
木構造 | フォルダ管理・データベース検索 | 階層的なデータ管理や高速探索に強い | 配列・リストでは表現が難しい |
ハッシュ | SNSのユーザー情報管理 | キーによる高速な検索(O(1))が可能 | リストや配列では検索に時間がかかる |
🎯 最後に|データ構造を学ぶとプログラムがもっと楽しくなる! データ構造は “整理整頓の考え方” そのもの。 どの仕組みを使うかで、プログラムの “速さ” や “効率” が大きく変わります。
用途や特徴を理解して、最適なデータ構造を選べるエンジニアを目指しましょう!
コメント