連結リスト

各要素が次の要素へのポインタを持つ線形データ構造

データ構造アルゴリズム

連結リストとは

連結リスト (Linked List) は、各要素 (ノード) が値と次のノードへのポインタを持つ線形データ構造である。配列と異なりメモリ上で連続している必要がなく、先頭・末尾への挿入・削除が O(1) で行える。

配列との比較

操作 配列 連結リスト
インデックスアクセス O(1) O(n)
先頭への挿入 O(n) O(1)
末尾への挿入 O(1) (動的配列) O(1) (末尾ポインタあり)
中間への挿入 O(n) O(1) (位置が既知)
検索 O(n) O(n)
メモリ 連続、キャッシュ効率が高い 分散、ポインタのオーバーヘッド

種類

単方向: [A][B][C] → null

双方向: null ← [A][B][C] → null

循環: [A][B][C][A] (先頭に戻る)

TypeScript での実装

class ListNode<T> {
  constructor(public value: T, public next: ListNode<T> | null = null) {}
}

class LinkedList<T> {
  private head: ListNode<T> | null = null;

  prepend(value: T) {
    this.head = new ListNode(value, this.head);
  }

  find(value: T): ListNode<T> | null {
    let current = this.head;
    while (current) {
      if (current.value === value) return current;
      current = current.next;
    }
    return null;
  }

  toArray(): T[] {
    const result: T[] = [];
    let current = this.head;
    while (current) {
      result.push(current.value);
      current = current.next;
    }
    return result;
  }
}

実際の利用場面

場面 理由
LRU キャッシュ 双方向リスト + ハッシュマップで O(1) の挿入・削除
ブラウザの履歴 前へ/次へのナビゲーション (双方向リスト)
Undo/Redo 操作履歴の管理
メモリアロケータ フリーリスト (空きメモリブロックの管理)

LRU キャッシュ

// 双方向連結リスト + Map で O(1) の LRU キャッシュ
class LRUCache<K, V> {
  private map = new Map<K, V>();
  constructor(private capacity: number) {}

  get(key: K): V | undefined {
    if (!this.map.has(key)) return undefined;
    const value = this.map.get(key)!;
    this.map.delete(key);
    this.map.set(key, value); // 末尾に移動 (最近使用)
    return value;
  }

  set(key: K, value: V) {
    this.map.delete(key);
    this.map.set(key, value);
    if (this.map.size > this.capacity) {
      this.map.delete(this.map.keys().next().value!); // 先頭を削除 (最も古い)
    }
  }
}

現代の開発での位置づけ

JavaScript/TypeScript では配列 (Array) が動的配列として実装されており、ほとんどのケースで配列の方が高速だ。連結リストが有利なのは、先頭への頻繁な挿入・削除が必要な場合に限られる。コーディング面接では頻出のデータ構造。

より深く学ぶには関連書籍が役立つ。

関連用語