データ構造 ・ O(1) 〜 O(n)

連結リスト

ノードが次のノードを指してつながる入れ物です。先頭への挿入は 1 回で済みます。

1 歩ずつ動かす

命令の列 (左から順に実行する)

  1. 先頭 (添字 0) に 1 を入れる
  2. 添字 2 に 7 を入れる
  3. 8 を削除
  4. 1 を削除
head作ったノード・外したノードはこの段に出ます空385
head から読んだ並び
3 → 8 → 5
いま見ているノード (cur)
なし
  • いま見ているノード (cur)
  • その前のノード (prev)
  • 作ったばかりのノード (下の行)
  • いま書き換えた矢印

1 歩目 / 全 16 歩実行した命令 0 個たどったノード 0 個ノード 3 個

リストは 3 → 8 → 5 です。head が先頭の 3 を指しています。

いま動いている疑似コードの行

  1. 1insert(index, x)
  2. 2新しいノード n を作り、x を入れる
  3. 3index が 0 なら、n.next を head にして、head を n にする
  4. 4そうでなければ、head から添字 index - 1 のノード p まで進む
  5. 5n.next を p.next にして、p.next を n にする
  6. 6delete(x)
  7. 7head から順に、値が x のノードを探す
  8. 8先頭なら head をその次にし、途中なら前のノードの next をその次にする

見本を変える

予想してみる

いまの歩の次は、ポインタを書き換える歩ではありません。「1 歩進む」で、書き換える手前まで進めましょう。

今回わかること

  • ノードが next でつながる形と、head の役目
  • 先頭と途中に挿入するときの、ポインタを付け替える順番
  • 削除のときに、前のノードの next を書き換える理由

前提

  • 配列と添字
  • 変数が別の値を指す (参照) という考え方

どんな入れ物か

連結リストは、値と「次のノードはどれか」を持つ小さな箱 (ノード) を、next でつないで並べる入れ物です。先頭を指す head を持っておけば、そこから next をたどって全部の値に行けます。

配列は値を隙間なく並べるので、途中に入れると後ろ全部をずらします。連結リストは next を 2 本付け替えるだけで済みます。そのかわり、添字 n の値を知るには head から n 回たどります。

手順と具体例

  1. 1新しいノードを作る (まだ列につながっていない)
  2. 2新しいノードの next を、入れる場所の次のノードにする
  3. 3前のノードの next (先頭なら head) を、新しいノードにする
  4. 4削除は、前のノードの next (先頭なら head) を、外すノードの次にする
最初のリストが 3 → 8 → 5 のとき、「ふつうの例」の命令を順に実行したとき
命令たどったノード実行したあとの並び
先頭 (添字 0) に 1 を入れる0 個1 → 3 → 8 → 5
添字 2 に 7 を入れる2 個1 → 3 → 7 → 8 → 5
8 を削除4 個1 → 3 → 7 → 5
1 を削除1 個3 → 7 → 5

たどる数と計算量

操作たどるノード計算量
先頭に入れる0 個 (head を付け替えるだけ)O(1)
添字 k に入れるk 個 (1 つ前のノードまで)O(n)
値を探して削除する最大 n 個 (無いときは全部)O(n)

n はノードの数です。入れる場所の 1 つ前のノードをもう手に持っているなら、途中への挿入も O(1) です。手間の差は、そこまでたどる回数から来ます。見本の「ふつうの例」では、たどったノードが 0 個、2 個、4 個、1 個でした。

使いどころ

向いているとき

  • 先頭への挿入と削除をくり返すとき (スタックを作る)
  • 途中への挿入が多く、配列で後ろをずらす手間が重いとき
  • 個数が決まっておらず、増えたり減ったりするとき

向いていないとき

  • 添字で値を何度も直接読みたいとき (配列を使う)
  • 値が小さく少ないとき (ノードごとに next の置き場が要る)

コード

疑似コード

  1. 1insert(index, x)
  2. 2新しいノード n を作り、x を入れる
  3. 3index が 0 なら、n.next を head にして、head を n にする
  4. 4そうでなければ、head から添字 index - 1 のノード p まで進む
  5. 5n.next を p.next にして、p.next を n にする
  6. 6delete(x)
  7. 7head から順に、値が x のノードを探す
  8. 8先頭なら head をその次にし、途中なら前のノードの next をその次にする

実際のコード

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

Python と JavaScript は、書き換えて「実行」を押すと、その場で結果が出ます。Java は読むだけの欄です。

よくある疑問

挿入のとき、前のノードの next を先に書き換えてはいけないのですか?
先に書き換えると、元の次のノードを指すものが無くなり、そこから先のノードに行けなくなります。新しいノードの next に元の次のノードをつないでから、前のノードの next を替えます。
外したノードは、どうなりますか?
どこからも指されなくなったノードは、Python、JavaScript、Java では自動で片付けられます。C 言語のように自分で解放する言語では、解放し忘れるとメモリが残ります。
配列とは、どう使い分けますか?
添字で何度も読むなら配列、先頭への出し入れが多いなら連結リストです。実際には、配列のほうがメモリ上で連続していて速いことが多いので、迷ったら配列から始めます。

関連