たくさんのデータをコンピュータの中でどう並べておくか。その「しまい方」のルールがデータ構造です。代表選手である配列とリストの違い、そして計算量の考え方を、身近なたとえから整理していきましょう。
先生、データ構造ってよく聞くけど、要するにデータの入れ物のことでしょ?
入れ物というより、入れ物の中の並べ方のルール、と言ったほうが近いわね。
同じデータでも、どう並べるかで使い勝手がガラッと変わるのよ。
まず基本中の基本が配列よ。
マンションの部屋みたいに、同じ大きさの箱がぴったり連続して並んでいるの。
マンションだと…101号室、102号室、って番号がついてますよねぇ?
まさにそれです。
その部屋番号にあたるのが添字(インデックス)ですね。
補足すると、多くの言語で添字は1ではなく0から始まります。
え、1番目が0なの? なんか気持ち悪いんだけど。
ふふ、最初は戸惑うわよね。
でもこれには理由があるの。
配列は先頭の住所(アドレス)から『何個ぶんずれた所か』で位置を計算するから、先頭そのものは『0個ずれ』なのよ。
だから添字を指定すれば、何番目でも一発で位置がわかる。
これをランダムアクセスと呼びます。
3000番目でも1番目と同じ速さで取り出せます。
わぁ、便利ですぅ。
じゃあ配列だけあれば十分なのではぁ…?
それが、そうもいかないのよ。
配列には弱点があってね。
途中にデータを差し込んだり、消したりするのが大変なの。
なんで? 1個入れるだけじゃん。
満員の映画館を想像してみて。
前のほうの席に1人割り込ませようとしたら、後ろの人が全員ひとつずつ席をずれないといけないでしょう?
あー、マジか。
それは面倒くさいやつだ。
そこで登場するのが連結リスト(リンクトリスト)です。
各データが、次のデータの居場所メモ(ポインタ)を持っていて、数珠つなぎになっています。
宝探しみたいですねぇ。
『次はあそこだよ』ってメモを辿っていく感じなのぉ?
いい例えです。
だから挿入や削除は、前後のメモの行き先を書き換えるだけ。
席をずらす必要がありません。
じゃあ連結リストのほうが強いじゃん!
ところが、こちらにも弱点があるの。
連結リストは添字でいきなり飛べないのよ。
100番目が欲しければ、先頭から99回メモを辿るしかないの。
あー、なるほど。
どっちも一長一短、トレードオフってやつだね。
それを数値で表したのが計算量です。
配列のアクセスは件数によらず一定なので O(1)、連結リストは件数ぶん辿るので O(n)。
挿入・削除はちょうど逆になります。
おぉ…数字の n が出てくると急に難しく見えますぅ。
大丈夫よ。
O(1)は『データが何個あっても手間は変わらない』、O(n)は『データが増えた分だけ手間も増える』。
それだけ覚えておけば十分。
ところで先生、Pythonのリストって、配列なの? 連結リストなの?
いい質問。
実はそのどちらでもなくて、両者のいいとこ取りをした動的配列という仲間なの。
中身は配列なので添字アクセスは O(1) と速い。
でも要素が増えて満杯になると、自動で大きな箱を作り直して引っ越してくれます。
JavaのArrayListも同じ仲間ですね。
へぇ〜。
サイズを気にせず追加できるのは、裏でこっそり引っ越してくれてたからなんですねぇ!
ちなみに、最初からサイズが固定で変えられない配列は静的配列と呼ぶわ。
動的配列と対で覚えておくといいわよ。
あと試験では多次元配列も出ます。
1次元が一列の名簿なら、2次元は縦横のある表、と考えると分かりやすいです。
結局、自分でプログラム書くときはどれを選べばいいわけ?
普段はPythonのlistのような動的配列でほぼ困りません。
連結リストは、頻繁に途中の出し入れをする特殊な場面で選ばれる程度です。
まとめるわね。
配列は『番地で一発アクセス、でも割り込みは苦手』、連結リストは『割り込み得意、でも頭から辿るのは苦手』。
この対比と、添字アクセスが O(1) という点を押さえれば、試験はばっちりよ。
確認クイズ
配列の要素に添字(インデックス)で直接アクセスする場合の計算量として正しいものはどれか。
- O(1)
- O(log n)
- O(n)
- O(n²)
こたえを見る
正解: 1. O(1)
正解はO(1)(定数時間)。配列はメモリ上に連続して格納されているため、先頭アドレス+添字×要素サイズで位置を即座に計算でき、要素数に関係なく一定時間でアクセスできます。O(log n)は二分探索など、O(n)は連結リストのように先頭から順に辿る場合、O(n²)は二重ループの処理などにあたり、いずれも配列の添字アクセスには当てはまりません。