【図解付き】データ構造とは?初心者向けに実例&Javaコードで徹底解説

未分類

はじめに|データ構造とは?プログラムの中の「整理整頓」

プログラムの世界では、たくさんのデータを決められた形で整理・保存する必要があります。データをどう整理するかを決める仕組みが データ構造(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))が可能リストや配列では検索に時間がかかる

🎯 最後に|データ構造を学ぶとプログラムがもっと楽しくなる! データ構造は “整理整頓の考え方” そのもの。 どの仕組みを使うかで、プログラムの “速さ” や “効率” が大きく変わります。

用途や特徴を理解して、最適なデータ構造を選べるエンジニアを目指しましょう!

コメント

タイトルとURLをコピーしました