コンテンツにスキップ

第3回:配列とリスト

実験1:listで3つの操作を計測

同じ「追加」でも、場所によって速度が大きく違うことを確かめる。

import time

lst = list(range(100000))  # 10万件のデータ

# 操作A:5万番目を取得
start = time.time()
x = lst[50000]
print(f"アクセス:   {time.time() - start:.6f}秒")

# 操作B:先頭に挿入
start = time.time()
lst.insert(0, -1)
print(f"先頭挿入:   {time.time() - start:.6f}秒")

# 操作C:末尾に追加
start = time.time()
lst.append(-2)
print(f"末尾追加:   {time.time() - start:.6f}秒")

先頭挿入と末尾追加で3000倍ほどの差がつく。同じ「追加」なのに、なぜか?

配列(Array)とは

メモリ上に「連続して」データを並べる方式

  • インデックスから位置を計算できるので、アクセスは O(1)
  • 途中に挿入すると、後ろの要素を全部ずらす必要がある → O(n)
  • 末尾への追加は、誰もずらさなくていい → O(1)
  • 途中の削除も後ろをずらすので O(n)

実験1の結果(アクセス速い・先頭挿入遅い・末尾追加速い)はこれで説明できる。

演習3-2

配列で以下の操作をしたとき、それぞれ O(?) か答えよ。

  • a) A[999] にアクセスする
  • b) 先頭に要素を挿入する
  • c) 末尾の要素を削除する
  • d) 真ん中に要素を挿入する

実験2:dequeで同じ3つを計測

collections.deque を使って同じ3つの操作を計測する。

from collections import deque

dq = deque(range(100000))

# ここから自分で書く
# ヒント:
#   lst.insert(0, x)  →  dq.appendleft(x)
#   lst.append(x)     →  dq.append(x)

実行すると list と結果が逆転する(アクセスが遅く、先頭挿入が速い)。なぜ?

連結リスト(Linked List)とは

メモリ上に「バラバラに」データを置き、「次はどこ」というポインタでつなぐ方式

  • i 番目の要素を探すには先頭から順にたどるしかない → アクセス O(n)
  • 途中に挿入するときは、前後のポインタを書き換えるだけ → O(1)(位置が分かっている場合)
  • 末尾追加も末尾を知っていれば O(1)

実験2の結果(dequeはアクセスが遅く、先頭挿入が速い)はこれで説明できる。

演習3-3

連結リストで以下の操作をしたとき、それぞれ O(?) か答えよ。

  • a) 先頭の要素にアクセスする
  • b) n番目の要素にアクセスする
  • c) 先頭に要素を挿入する
  • d) 末尾に要素を追加する(末尾の位置を知っている場合)

配列 vs 連結リスト

操作 配列 連結リスト
i番目にアクセス O(1) O(n)
先頭に挿入 O(n) O(1)
末尾に追加 O(1) O(1) ※
途中に挿入 O(n) O(1) ※
途中を削除 O(n) O(1) ※

※ 位置が分かっている場合に限定。そうじゃないときは O(n)

全部が速い方法は存在しない

だから「何を優先するか」で選ぶ。これがデータ構造の設計判断である。同じ判断がデータベースの設計や検索エンジンの内部構造でも行われている。

演習3-4

配列と連結リストどちらが適しているか? 理由も1文で書いてください。

  1. クラス名簿から「特定の出席番号」の学生の名前を引く
  2. 50音順に並んだ名簿カードに、新しい人のカードを差し込む
  3. 注文履歴を発行順に溜めていく(あとで番号検索はしない)
  4. 順番待ちの列の途中で、キャンセルした人を削除する

補足:Python の list と deque

名前が紛らわしいので整理しておく。

名前 実体
Python の list 動的配列(サイズが足りなくなると自動で拡張)
Python の collections.deque 両端キュー(内部は連結リストに近い構造)
C / C++ の配列 静的配列(サイズ固定)
  • deque の読み方は「デック」、double-ended queue の略
  • 先頭・末尾どちらからも O(1) で追加・削除できる

実際の使われどころ

連結リスト単体で使われる場面は少ない。第8回以降の木構造・グラフ・ハッシュテーブルの内部で活用されることが多い。