データ構造の計算量、O(1)とかO(log n)とか、覚えましたよね。

でも実務では、教科書で速いはずの構造が実機で負けたり、O(1)が突然O(n)に化けたりします。理論と実機の間には、段差があるんです。今回は、「教科書で速いはずのLinkedListが、実は遅い」話から、その段差を8つ集めました。1つでも知らなかったら儲けものです。

① ArrayListのaddは、「たまに全コピー」です

list.add(x) の計算量はO(1)——平均は、です。

内部は固定長の配列で、埋まるとより大きい配列を新しく確保して、全要素をコピーします。この瞬間だけO(n)。

ただ、拡張のたびに容量を一定の倍率で増やしている(Javaでは約1.5倍)ので、コピーのコストを全体でならすとO(1)に収まります(ならし計算量)。

だから、要素数が読めているなら最初に容量を指定しておくと、途中の全コピーがゼロになる。「大量addの前に new ArrayList<>(想定数)」は、地味に効く一手なんです。

② HashMapのO(1)は、条件付きです

HashMapの取得はO(1)——ハッシュがうまく分散していれば、です。

複数のキーが同じ場所に集まると(衝突)、そこは連結リストになり、線形探索O(n)に劣化します。

そこでJavaのHashMapは、1箇所の衝突が8個を超えると(表がある程度大きくなっていれば)、その部分を木構造(赤黒木)に変えてO(log n)で耐えるという改良を入れています。

とはいえ大前提は、hashCodeがちゃんと分散すること。自作クラスをキーにするときにhashCodeを雑に実装すると、O(1)のはずが全部詰まる。「O(1)は、良いhashCodeの上に成り立つ約束」なんです。

③ LinkedListは、ほぼ全場面でArrayListに負けます

教科書では「途中の挿入削除はLinkedListが速い(O(1))」。でも実機では、ほとんどの場面でArrayListの方が速いんです。理由は2つ。

  • LinkedListの挿入がO(1)なのは「挿入位置を既に持っている」場合だけで、位置まで辿るのにO(n)かかる
  • ノードがメモリ上に散らばるので、CPUのキャッシュがまったく効かない

ArrayListは連続メモリなので、多少のコピーを払ってもキャッシュの恩恵が勝つ。

「計算量では勝っているのに、実機では負ける」の代表例。JavaのLinkedListを書いたジョシュア・ブロック本人が「自分が書いたけど、自分では使わない」と言っているほどです。

④ 配列の途中削除には、2つの流派があります

配列から要素を1個消したい。真ん中に穴が空きますよね。

要件やり方計算量
順序を保つ後ろを全部前に詰めるO(n)
順序は要らない最後の要素を穴に移動して、末尾を縮めるO(1)

この「末尾と入れ替え(swap-remove)」は、知らないと絶対に思いつかない一手です。

「並び順はどうでもいいコレクション」なら、削除が劇的に速くなる。順序という要件を1つ捨てるだけで、計算量が変わるんです。

⑤ スタックとキューは、実は「使い方の名前」です

スタック(後入れ先出し)とキュー(先入れ先出し)。別々の構造だと思っていませんか。

実はどちらも、配列やリストの「使い方の制約」にすぎません。両端のどちらから出し入れするかを決めただけ。

そして深いのがここで。「探索の途中を、スタックで管理すれば深さ優先、キューにすれば幅優先」になります。同じ探索コードの、データ構造を差し替えるだけで挙動が変わる。

データ構造は「入れ物」であると同時に、「アルゴリズムの性格を決めるつまみ」でもあるんです。

⑥ 「1位から3位だけ欲しい」に、全ソートは要りません

100万件から上位3件を取りたい。全部ソート(O(n log n))していませんか。

ヒープ(優先度付きキュー)を使えば、3件ぶんの小さなヒープを保ちながら全体を1回舐めるだけ。計算量はO(n log k)で、kが小さければほぼO(n)です。

「常に最大(最小)を効率よく取り出す」のがヒープの仕事。ランキング、締切の近いタスク、経路探索の次候補選び——全体を並べずに、欲しいものだけ取り出す場面で効きます。

全ソートは、実は贅沢な選択だったりするんです。

⑦ 「あるか無いか」だけなら、ほぼゼロのメモリで判定できます

巨大なデータに対して「この要素、含まれてる?」を高速・省メモリで知りたい。

ブルームフィルタという構造があります。要素そのものを保存せず、ビット列だけで管理する。特徴が独特で——「無い」は確実、「ある」は“たぶんある”(ごくまれに誤検知する)。

この割り切りのおかげで、桁違いに小さいメモリで判定できます。「DBを引く前のふるい」として使えば、無駄なアクセスの大半を手前で弾ける。

100%の正確さを少し譲る代わりに、メモリを桁で得する。実務の巨大データで効く発想なんです。

⑧ 正しいデータ構造は、「操作の頻度」から決まります

最後に、選び方の話でまとめます。

データ構造選びは、計算量表の暗記ではありません。「その中で、何を一番多くやるか」で決まります。

一番多くやる操作選ぶ構造
末尾に足して順に読む配列(ArrayList)
キーで引くハッシュマップ
常に最大/最小が欲しいヒープ
順序を保った範囲検索木(TreeMap)
含有チェックだけ、省メモリ最優先ブルームフィルタ

「一番頻繁な操作が速い構造」を選び、たまの操作の遅さは許容する。この視点で選べる人が、実機で速いコードを書くんです。

番外編:知っておくと得する小ネタ

文字列の += は、ループ内で罠になります
文字列は多くの言語で不変なので、ループ内の連結は毎回全コピー=O(n²)。専用のビルダー(StringBuilder等)を使うのが定石です。

「だいたい合ってる個数」も数えられます
正確なユニーク数え上げは重いですが、HyperLogLogという構造ならわずかなメモリで“だいたいの個数”を出せます。⑦と同じ「正確さを少し譲って桁で得する」仲間ですね。

配列とリストの違いは、住所です
配列は「連番の部屋」、リストは「次の部屋の地図を持った部屋」。この住所の違いが、③のキャッシュ性能の差を生むんです。

RJCでも、データ構造と向き合っています

大量データを扱う場面では、構造を1つ替えるだけで処理時間が桁で変わることがあります。⑧の「操作の頻度から選ぶ」は、設計レビューで効く視点なんですよね。

こういう仕組みの話を、Techチームで共有しながら開発しています。

よくいただくご質問

Q. まずどれから押さえるべきですか?
A. ⑧の選び方かなと思います。個々の構造の暗記より、「何を一番やるかで選ぶ」という軸を持つ方が、実務では効くんですよね。

Q. 結局、迷ったら何を使えばいいですか?
A. 多くの場面はArrayListとHashMapで戦えます。③のとおり、なんとなくLinkedList、が一番もったいないんです。

Q. 計算量は、どこまで覚えるべきですか?
A. 主要な操作の「速い・遅い」の感覚があれば十分です。正確な記号より、「これはたまに重い(①)」「これは条件付き(②)」を知っている方が実戦的です。

Q. 未経験からでも、分かるようになりますか?
A. なります。データ構造は「本棚・引き出し・行列・トーナメント表」といった日常の道具の比喩で、性格が全部つかめるんです。

計算量は入口で、出口はメモリの形です

今日の8つに共通していたのは、これでした。

計算量の理論は、データ構造選びの入口です。でも実機での勝敗は、メモリの並び(③)、操作の頻度(⑧)、要件の割り切り(④⑦)が決めます。

表を暗記した人ではなく、「この場面で一番やる操作は何か」を問える人が、速い構造を選ぶんですよね。

みなさんが「構造を替えたら速くなった」体験、よかったら教えてください。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?