第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文で書いてください。
- クラス名簿から「特定の出席番号」の学生の名前を引く
- 50音順に並んだ名簿カードに、新しい人のカードを差し込む
- 注文履歴を発行順に溜めていく(あとで番号検索はしない)
- 順番待ちの列の途中で、キャンセルした人を削除する
補足:Python の list と deque¶
名前が紛らわしいので整理しておく。
| 名前 | 実体 |
|---|---|
Python の list |
動的配列(サイズが足りなくなると自動で拡張) |
Python の collections.deque |
両端キュー(内部は連結リストに近い構造) |
| C / C++ の配列 | 静的配列(サイズ固定) |
dequeの読み方は「デック」、double-ended queue の略- 先頭・末尾どちらからも O(1) で追加・削除できる
実際の使われどころ
連結リスト単体で使われる場面は少ない。第8回以降の木構造・グラフ・ハッシュテーブルの内部で活用されることが多い。