計算量オーダー(Big-O)エクスプローラー

入力サイズNが増えるにつれて、アルゴリズムの計算量クラスがどう増加するかを見てみましょう。

インタラクティブシミュレーションを読み込んでいます...

レッスン

理論 — 計算量オーダー(Big-O)エクスプローラー

Big-Oは時間の測定ではなく、増加速度の上限を示すものです。あるアルゴリズムが O(N²) であると言うことは、ある入力サイズを超えると、その処理量が の定数倍以下に収まることを主張しているに過ぎません — そこには実行秒数に関する言及はなく、小さな入力についての主張も一切含まれていません。

各記号の意味

N
入力サイズ — アルゴリズムに渡される要素の数。上記で設定した2から1,000,000までの数値です。
f(N)
そのサイズで実際に実行される計算量 — 秒数ではなく抽象的な操作回数でカウントされます。
c
表記上、隠すことが許されている定数倍の係数。ここでの主張は f(N) ≤ c·g(N) であり、c は 2 かもしれませんし 2000 かもしれません。そしてそれこそが、Big-Oが意図的に切り捨てる情報です。
n₀
上限が成立しなければならない境界となるサイズn₀ 未満では、各計算量クラスの大小関係はどのようになっていてもかまいません。上記の5つの数値が小さな N で密集しているのはそのためです。

公式の導き方

  1. 厳密にする必要のある主張から始めましょう。すなわち、計算量 f(N) は最終的に、ある基準関数 g(N) よりも速く増加することはない、という主張です。
  2. “より速く増加することはない”という表現は、定数倍の差を許容する必要があります。なぜなら、内部ループを最適化しても変わるのは定数係数であり、グラフの形状ではないからです。したがって、係数を許容します:f(N) ≤ c·g(N)
  3. また、“最終的に”という条件は、何が起こるかわからない小さな入力を除外するためのものです。不等式が成り立つことを N ≥ n₀ 以降でのみ求めます。これらを合わせると:f(N) = O(g(N)) とは、すべての N ≥ n₀ に対して f(N) ≤ c·g(N) を満たすような、ある c > 0 とある n₀ が存在することを意味します。

表示の読み方

5つの行は、すべての定数を 1 に設定した状態で、1つの入力サイズを5つの増加クラスに適用した結果を示しています — したがってこれらは実行時間ではなく、操作回数です。デフォルトの N = 100 では、それらの値は 1710066410,000 となります。対数の底は 2 であり(log₂ 100 ≈ 6.64)、そのため O(log N) は 7 を示し、O(N log N) は 700 ではなく 664 を示します。

前提
すべての操作コストが同等であり、カウントが実測値ではなく厳密な計算値であると仮定しています。これによって比較が明快になる一方で、同時にモデルが抽象的なものとなります — メモリアクセスパターン、キャッシュの挙動、ディスク処理などはすべてこのモデルの対象外であり、実際のハードウェア上では、それらが2つのアルゴリズムのどちらが優れているかを決める決定打となることが多々あります。
成り立たない場合
この上限は n₀ 未満では何も保証しません。それは実際に確かめることができます。N = 2 に設定すると、5つのクラスは 11224 となり — ほとんど区別がつきません。記法が保証する大小関係は N が十分に大きくなって初めて現れます。だからこそ、定数倍が小さな O(N²) の手法が、実際に向き合うあらゆる入力サイズにおいて O(N log N) の手法を上回るということが起こり得るのです。

増加が最適化を上回るとき 🖖

Big-Oは入力サイズが増えるにつれて処理量がどう増加するかを表します。単一のループはおおよそNに比例して増加し、入れ子のループはしばしばN²に比例して増加し、分岐する再帰は指数関数的に増加することがあります。重要な教訓はスケールです。Nが小さいうちは多くの手法が似て見えますが、Nが大きくなると増加クラスが実行時間を支配します。

2倍テストで見抜く 🖖

増加クラスを最も直感的につかむには、入力を2倍にして作業量の変化を見ることです。O(log N) ではほとんど動かず — 二分探索は100万個の中から1個を約20回の比較で見つけます。O(N) では作業量が2倍になり、O(N2) では4倍になります。このツールで N を動かせば、その差が見えない状態から圧倒的な状態へ広がる様子がわかります。

速いはずのクラスが負けるとき 🖖

増加クラスが低くても、プログラムが速いとは限りません。計算機科学者はその例外を銀河的アルゴリズムと呼びます。Big-O は優れていても、隠れた定数係数が途方もなく大きいため、物理的な宇宙にあるどんなものより大きな入力でしか単純な手法を上回らない手法です。記録を持つ行列乗算アルゴリズムのいくつかが実際には決して使われないのは、まさにこのためです。Big-O は実際の速度を左右する定数を、こっそり切り捨てているのです。

全プロセスの詳細解説

  1. N log N と N ² の間に隔たりが生じる箇所 5 ステップ

    N = 100 のとき、パネルには N log N に対して 664、N² に対して 10 000 と表示される。それはわずか15倍の差に過ぎず、計算複雑性クラスがもたらすとされる決定的な格差とは程遠い。その格差が実際にどこで開くのかを突き止めよ。

    1. まず2つの数値から始める。log₂ 100 は 6.6439 であるため、N log₂ N は 664 となり、N² は 10 000 となる。

    2. それらの比率は定数ではなく、それこそが計算複雑性クラスの真髄である。割り算を行うと N が1回約分され、N/log N が残る — これは、成長速度こそ遅いものの、限りなく増大する量である。

    3. N = 100 において、その値は 15.1 である。これは実在する差ではあるが、目覚ましいものではない。15倍の高速化はより優れた定数係数によってもたらされ得る程度のものであり、小さな入力に対するベンチマークが人を誤解させるのはまさにこれが理由である。

    4. ここで100万を入力してみる。対数は 6.6 から 19.9 へと3倍にしか増えていないのに対し、N は1万倍に成長している。現在の比率は 50 172 である。

    5. そして、この傾向が逆転することはない。N/log N の導関数は、Ne を超えるすべての範囲において正となるため、2次アルゴリズムが追いつくような入力サイズは存在しない。

    解答

    ツールには N = 100 において 66410 000 と表示される。覚えておくべき数値はもう一方である。100万において、これら同じ2つの曲線は 50 172 も離れる。計算複雑性クラスとは100個の要素についての主張ではなく、そこを比較することは誤ったアルゴリズムを選択するように自らを説得してしまう典型的な方法である — 15倍の差は、より高速なプログラミング言語を使えば埋められるように思えてしまう。スライダーを上に動かして、比率がそれに伴って増大するのを確認してほしい。実務において対数が軽視されがちであるのもこれが理由である。入力が1万倍に増える間に、対数は3倍にしか増えなかったのである。

学習の道すじ

秒ではなく仕事量を数える

この次に sorting-race 実行時間ではなく操作回数を。五つの行は定数をすべて 1 にした仕事量なので、秒については何も語りません。次のツールはまさにそういう数値 —展開したノード数— を表示するので、四つの探索アルゴリズムを一つの格子上で、時計を使わずに比べられます。

参考文献 (1)

例題