広告スペース

第29回 データ構造入門

C言語で学ぶデータ構造。連結リスト・スタック・キューの実装と可視化。

データ構造とは ― データの「並べ方」を設計する

プログラムが扱うデータをどのようにメモリ上に配置し、どのように操作するかを決める仕組みが「データ構造」です。配列も立派なデータ構造の一つですが、配列だけでは不便な場面があります。

配列の限界

途中への挿入が遅い
先頭や途中にデータを入れるには、後ろの要素を全部ずらす必要がある。100万件なら100万回の移動。
サイズが固定
宣言時にサイズを決める必要がある。途中で増減するデータには不向き(mallocで多少改善できるが手間)。

代表的なデータ構造

構造特徴用途例
連結リスト要素同士をポインタで繋ぐ。挿入・削除が高速順序付きデータの頻繁な追加・削除
スタック後入れ先出し(LIFO)関数の呼び出し管理、Undo機能
キュー先入れ先出し(FIFO)印刷待ち行列、タスク処理順
これらはすべて構造体+ポインタ+mallocの組み合わせで実装します。ここまで学んだ知識の集大成です!

連結リスト ― ポインタで繋ぐデータ列

連結リスト(Linked List)は各要素(ノード)がデータ次のノードへのポインタを持つ構造です。

ノードの定義

typedef struct Node {
  int data;              // 格納するデータ
  struct Node *next;     // 次のノードへのポインタ
} Node;

メモリ上のイメージ

10
next→
20
next→
30
next→
NULL

主要な操作

// ① ノードの生成
Node* createNode(int val) {
  Node *n = malloc(sizeof(Node));
  n->data = val;
  n->next = NULL;
  return n;
}

// ② 先頭に挿入(O(1)で速い!)
void pushFront(Node **head, int val) {
  Node *n = createNode(val);
  n->next = *head;     // 新ノードの次を旧先頭に
  *head = n;           // 先頭ポインタを更新
}

// ③ 全要素の表示
void printList(Node *head) {
  for (Node *p = head; p != NULL; p = p->next)
    printf("%d → ", p->data);
  printf("NULL\n");
}

// ④ メモリの解放(忘れるとリーク!)
void freeList(Node *head) {
  while (head) {
    Node *tmp = head->next;
    free(head);
    head = tmp;
  }
}
挿入(先頭)
O(1) — 超高速
検索
O(n) — 先頭から順にたどる
配列との違い
添字アクセス不可、ランダムアクセスは苦手

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

スタックは最後に入れたものを最初に取り出す構造です。お皿を積み重ねるイメージ。関数の呼び出しもスタックで管理されています。
スタックのイメージ
10
20
30
← TOP(ここから出す)
// push: 先頭に追加
void push(Node **top, int val) {
  Node *n = createNode(val);
  n->next = *top;
  *top = n;
}

// pop: 先頭を取り出す
int pop(Node **top) {
  if (*top == NULL) return -1;
  Node *tmp = *top;
  int val = tmp->data;
  *top = tmp->next;
  free(tmp);
  return val;
}
身近な例: ブラウザの「戻る」ボタン、Ctrl+Z(元に戻す)、関数の呼び出し管理(コールスタック)はすべてスタック構造です。

キュー ― 先入れ先出し(FIFO)

キューは最初に入れたものを最初に取り出す構造です。レジの行列と同じ。
キューのイメージ
出口←
10
20
30
→入口
// enqueue: 末尾に追加
void enqueue(Node **head, Node **tail, int val) {
  Node *n = createNode(val);
  if (*tail) (*tail)->next = n;
  else *head = n;
  *tail = n;
}

// dequeue: 先頭を取り出す
int dequeue(Node **head, Node **tail) {
  if (!*head) return -1;
  Node *tmp = *head;
  int val = tmp->data;
  *head = tmp->next;
  if (!*head) *tail = NULL;
  free(tmp);
  return val;
}
身近な例: プリンタの印刷待ち、OSのタスクスケジューラ、チャットの送信順など。

配列 vs 連結リスト vs スタック vs キュー

操作配列連結リストスタックキュー
先頭に挿入O(n)O(1)
末尾に追加O(1)O(1)*O(1)O(1)
ランダムアクセスO(1)O(n)
途中に挿入O(n)O(1)†
* tail ポインタ保持時。 † 挿入位置が既知の場合。

連結リスト操作デモ

ボタンでリストに要素を追加・削除する様子をリアルタイムで確認できます。
リストの状態:
(空のリスト)
ボタンを押してリストを操作してみましょう。
広告スペース

関連する講座

発展編
第28回 動的メモリ(malloc/free)
C言語の動的メモリ確保。malloc, free, メモリリークの原因と対策。
発展編
第25回 ポインタの基礎
C言語のポインタをメモリ可視化で理解。アドレスと間接参照を図解。
アルゴリズム編
第30回 ソートアルゴリズム
C言語のソートアルゴリズム。バブル・選択・挿入ソートをアニメーションで可視化。
← 前の講座
第28回 動的メモリ(malloc/free)
次の講座 →
第30回 ソートアルゴリズム

確認クイズ

この講座の理解度をチェックしましょう!

Q1. 連結リストの利点は?

ランダムアクセスが速い
要素の挿入・削除が効率的
メモリ使用量が少ない

連結リストはポインタを付け替えるだけで挿入・削除ができます。配列は要素をずらす必要があり非効率です。

Q2. スタックのデータ取り出し順序は?

FIFO(先入れ先出し)
LIFO(後入れ先出し)
ランダム

スタックは最後に入れたデータを最初に取り出す LIFO(Last In, First Out)構造です。

Q3. キューのデータ取り出し順序は?

FIFO(先入れ先出し)
LIFO(後入れ先出し)
優先度順

キューは最初に入れたデータを最初に取り出す FIFO(First In, First Out)構造です。行列と同じイメージです。

この記事をシェア
X(Twitter)でシェア Facebookでシェア LINEで送る はてブ

この講座の理解を深めるおすすめ書籍

サイトで動きを理解し、書籍で演習量を補うと効果的です

📘
苦しんで覚えるC言語
MMGames 著
初心者向けの定番入門書。丁寧な解説で基礎を固められます。
Amazonで見る
📗
新・明解C言語 入門編
柴田望洋 著
図解が豊富で、演習問題も充実。大学の教科書としても採用多数。
Amazonで見る
📙
プログラミング言語C 第2版
B.W.カーニハン, D.M.リッチー 著
通称K&R。C言語の原典。基礎を終えた後のステップアップに最適。
Amazonで見る

※ 上記リンクはアフィリエイトリンクです。購入によりサイト運営を支援いただけます。