コンテンツにスキップ

計算量と O 記法:アルゴリズムの速さを入力サイズの関数で測る

生 Markdown
  • 計算量とは「入力サイズ nn に対して実行される基本演算の回数」であり、秒で測った実行時間そのものではありません。この抽象化によって、機種・言語・コンパイラに依存しない比較ができます。
  • f(n)=O(g(n))f(n) = O(g(n)) は「ある定数 c>0c > 0n0n_0 が存在して、nn0n \ge n_0 を満たすすべての nnf(n)cg(n)f(n) \le c\,g(n)」という主張です。O(g)O(g) は関数の集合であり、等号は慣用的な略記にすぎません。
  • 増大度には 1lognnεnnlognn22nn!1 \prec \log n \prec n^{\varepsilon} \prec n \prec n\log n \prec n^2 \prec 2^n \prec n! という階層があります。これは感覚ではなく、極限として証明できる定理です。
  • 入力サイズを 2 倍にすると、Θ(n2)\Theta(n^2) のアルゴリズムは 4 倍、Θ(2n)\Theta(2^n) のアルゴリズムは「それまでの全計算量ぶん」時間が増えます。計算機を 1000 倍速くしても、Θ(2n)\Theta(2^n) で扱える nn は約 10 しか増えません。
  • 分割統治法の漸化式 T(n)=aT(n/b)+f(n)T(n) = a\,T(n/b) + f(n) は、マスター定理によって機械的にオーダーが決まります。
  • 「多項式時間で解けるか、指数時間しか知られていないか」という境界が、計算機科学最大の未解決問題である P≠NP 予想の中心にあります。

1. 動機:なぜ「秒」ではなく「増大度」で測るのか

Section titled “1. 動機:なぜ「秒」ではなく「増大度」で測るのか”

二つのプログラム A と B のどちらが速いかを知りたいとします。素朴な方法は、実際に走らせて秒数を比べることです。しかしこの方法には決定的な弱点があります。測定したのは「その入力・その計算機・そのコンパイラ・そのときのキャッシュ状態」での速さであって、明日別の環境で同じ順位になる保証がないのです。

もっと悪いことに、小さな入力での測定は大きな入力での挙動をまったく予言しません。要素数 nn の配列を整列する二つのアルゴリズムを考えます。挿入ソートは最悪の場合およそ n2/2n^2/2 回の比較を行い、マージソートはおよそ nlog2nn\log_2 n 回の比較を行います。n=10n = 10 なら前者は 45 回、後者は 33 回で、ほとんど差がありません。ところが n=106n = 10^6 では前者が 5×10115 \times 10^{11} 回、後者が 2×1072 \times 10^7 回となり、比は約 25000 倍に開きます。1 秒間に 10910^9 回の基本演算をこなす計算機なら、後者は 0.02 秒、前者は 8 分以上かかります。

ここで効いているのは実装の巧拙ではなく、nn が増えたときに演算回数がどう増えるかという構造です。ハードウェアの改良やコードの最適化がもたらすのは、たいていの場合「定数倍の高速化」です。一方アルゴリズムの選択は、n2n^2nlognn\log n に変えるという、定数倍では埋まらない差をもたらします。

そこで私たちは、演算回数を nn の関数として捉え、定数倍と有限個の例外を無視した「増大度」だけを見ることにします。この粗さこそが、環境に依存しない普遍的な比較を可能にします。以下ではまず何を数えるのかを決め(§2)、次に増大度の比較を厳密な言葉にし(§3)、その階層を証明し(§4)、実際のスケールで何が起きるかを見ます(§5)。

「基本演算の回数」を数えるには、何が基本演算なのかを先に決めなければなりません。標準的に用いられるのが一様コスト RAM モデル(uniform-cost random access machine)です。

定義 2.1一様コスト RAM モデル

計算機は、番地 0,1,2,0, 1, 2, \ldots で添字づけられた記憶セルの列と、有限個のレジスタを持つとする。次の各操作を基本演算と呼び、いずれも 1 単位時間で実行されるものとする。

  1. 定数、レジスタ、および番地を指定した記憶セルからの読み出しと書き込み
  2. 整数・実数の加減乗除と比較
  3. 条件分岐と無条件分岐

さらに、1 個の記憶セルには O(logn)O(\log n) ビットの語(nn は入力サイズ)が格納できるものとする。

最後の条件は見落とされがちですが本質的です。語長を O(logn)O(\log n) ビットに制限しておかないと、1 個のセルに入力全体を詰め込んで多倍長演算を 1 ステップで済ませる、という現実離れしたアルゴリズムが許されてしまいます。逆にこの制限のもとでは、nn 個の要素に添字を振るのに必要な log2n\log_2 n ビットはちょうど 1 語に収まり、配列の添字計算が 1 ステップで行えるという、実際の計算機に近い設定になります。

注意 2.2

一様コストモデルでは、kk 桁の整数どうしの乗算も 1 ステップと数えます。暗号や計算機代数のように巨大整数を扱う場面ではこの仮定は成り立たず、ビット数に比例したコストを課す対数コストモデルを使います。どのモデルで測っているかを明示しないと、計算量の主張は意味を持ちません。

定義 2.3最悪時間計算量・空間計算量

アルゴリズム AA と入力 xx に対し、AAxx 上で停止するまでに実行する基本演算の回数を tA(x)t_A(x)、書き込みまたは読み出しを行った記憶セルの総数を sA(x)s_A(x) と書く。入力 xxサイズ x|x| を、xx を表現するのに要する語数と定める。このとき

TA(n)=maxx=ntA(x),SA(n)=maxx=nsA(x)T_A(n) = \max_{|x| = n} t_A(x), \qquad S_A(n) = \max_{|x| = n} s_A(x)

をそれぞれ AA最悪時間計算量最悪空間計算量と呼ぶ。

max\max を取っている点に注意してください。時間計算量は「サイズ nn の入力のうち最も不利なもの」に対する値です。したがって TA(n)T_A(n) は、nn さえ決まればどんな入力でも保証される上限になります。

時間と空間は独立ではありません。次の命題は、空間の方が時間より「安い」資源であることを述べています。

命題 2.4空間は時間で抑えられる

アルゴリズム AA が、1 回の基本演算で高々 κ\kappa 個の記憶セルにアクセスするとする(定義 2.1 のモデルではつねに κ3\kappa \le 3 と取れる)。このとき、すべての nn に対して

SA(n)n+κTA(n)S_A(n) \le n + \kappa\, T_A(n)

が成り立つ。とくに TA(n)nT_A(n) \ge n ならば SA(n)=O(TA(n))S_A(n) = O(T_A(n)) である。

証明(命題 2.4)

サイズ nn の入力 xx を固定します。AA がアクセスするセルは、入力を格納した nn 個のセルか、実行中にアクセスされたセルのいずれかです。仮定より 1 ステップでアクセスされるセルは高々 κ\kappa 個なので、tA(x)t_A(x) ステップ全体でアクセスされるセルは高々 κtA(x)\kappa\,t_A(x) 個です。よって sA(x)n+κtA(x)n+κTA(n)s_A(x) \le n + \kappa\, t_A(x) \le n + \kappa\,T_A(n) となり、x=n|x| = n について最大を取れば第 1 の主張を得ます。

TA(n)nT_A(n) \ge n のときは n+κTA(n)(1+κ)TA(n)n + \kappa T_A(n) \le (1+\kappa) T_A(n) なので、定義 3.1 の定数を c=1+κc = 1 + \kappan0=1n_0 = 1 と取れば SA(n)=O(TA(n))S_A(n) = O(T_A(n)) です。

逆は成り立ちません。空間 O(1)O(1) で時間 Θ(2n)\Theta(2^n) のアルゴリズムはいくらでも作れます。「メモリは使い回せるが、時間は使い回せない」という非対称性がここに現れています。

例 2.5挿入ソートの比較回数を最後まで数える

長さ nn の配列を昇順に並べ替える挿入ソートを考えます。

def insertion_sort(a):
for i in range(1, len(a)):
key = a[i]
j = i - 1
while j >= 0 and a[j] > key:
a[j + 1] = a[j]
j -= 1
a[j + 1] = key
return a

要素どうしの比較 a[j] > key が何回評価されるかを数えます。外側のループ変数 ii を固定すると、内側の whilej=i1,i2,j = i-1, i-2, \ldots と減らしながら回ります。j >= 0 が偽になった時点で短絡評価により比較は行われないので、比較回数は jj00 以上である間の回数、すなわち高々 ii 回です。この上限は、入力が狭義単調減少列 a=(n,n1,,1)a = (n, n-1, \ldots, 1) のときちょうど達成されます。実際このとき key はつねに a[0..i1]a[0..i-1] のどの要素よりも小さいので、whilej=1j = -1 になるまで回り、比較は j=i1,,0j = i-1, \ldots, 0ii 回です。したがって最悪比較回数は

i=1n1i=n(n1)2\sum_{i=1}^{n-1} i = \frac{n(n-1)}{2}

です。同じ入力に対する代入回数は、ii ごとに a[j+1]=a[j]a[j+1] = a[j]ii 回、key の読み書きが 2 回なので i=1n1(i+2)=n(n1)2+2(n1)\sum_{i=1}^{n-1}(i+2) = \frac{n(n-1)}{2} + 2(n-1) 回です。基本演算の総数はループ制御を含めても定数倍しか変わらないので、T(n)=Θ(n2)T(n) = \Theta(n^2) となります(Θ\Theta の意味は 定義 3.1 で与えます)。

一方、使う記憶領域は入力の配列に加えて i, j, key の 3 語だけなので、追加空間は Θ(1)\Theta(1) です。

注意 2.6

最悪計算量のほかに、入力の確率分布を仮定して期待値を取る平均計算量、最も有利な入力での最良計算量も定義できます。挿入ソート(例 2.5) の最良計算量は、すでに整列済みの入力に対して比較 n1n-1 回、すなわち Θ(n)\Theta(n) です。クイックソートは最悪 Θ(n2)\Theta(n^2) でありながら、ランダムな順列上の平均は Θ(nlogn)\Theta(n\log n) であり(定理 5.5[ソートアルゴリズム])、実用上はこの平均が効きます。詳しくは ソートアルゴリズム を参照してください。断りがない限り、本記事の計算量はすべて最悪計算量です。

以下、f,gf, gN={1,2,}\mathbb{N} = \{1, 2, \ldots\} 上で定義された非負実数値関数とします。

定義 3.1O 記法・Ω 記法・Θ 記法

関数 gg に対し、関数の集合 O(g)O(g), Ω(g)\Omega(g), Θ(g)\Theta(g) を次で定める。

O(g)={f  :  c>0, n0N, nn0, f(n)cg(n)}Ω(g)={f  :  c>0, n0N, nn0, f(n)cg(n)}Θ(g)=O(g)Ω(g)\begin{aligned} O(g) &= \{\, f \;:\; \exists c > 0,\ \exists n_0 \in \mathbb{N},\ \forall n \ge n_0,\ f(n) \le c\,g(n) \,\} \\ \Omega(g) &= \{\, f \;:\; \exists c > 0,\ \exists n_0 \in \mathbb{N},\ \forall n \ge n_0,\ f(n) \ge c\,g(n) \,\} \\ \Theta(g) &= O(g) \cap \Omega(g) \end{aligned}

fO(g)f \in O(g) を慣用的に f(n)=O(g(n))f(n) = O(g(n)) とも書き、「ff は高々 gg のオーダーである」と読む。

定義の量化子の順序が要です。ccn0n_0nn より先に選ばれます。つまり「nn ごとに都合のよい cc を選ぶ」ことは許されません。この順序を破ると、任意の f,gf, gg>0g > 0)について fO(g)f \in O(g) が成り立ってしまい、記法は無意味になります。

flowchart LR
A["定数 c と n0 を先に選ぶ"] --> B["以後どんな n でも"] --> C["n が n0 以上なら f(n) は c·g(n) 以下"]
O 記法の定義が主張していること

oo 記法と ω\omega 記法は、量化子を一つ強めたものです。

定義 3.2o 記法・ω 記法

o(g)={f  :  c>0, n0N, nn0, f(n)cg(n)}ω(g)={f  :  c>0, n0N, nn0, f(n)cg(n)}\begin{aligned} o(g) &= \{\, f \;:\; \forall c > 0,\ \exists n_0 \in \mathbb{N},\ \forall n \ge n_0,\ f(n) \le c\,g(n) \,\} \\ \omega(g) &= \{\, f \;:\; \forall c > 0,\ \exists n_0 \in \mathbb{N},\ \forall n \ge n_0,\ f(n) \ge c\,g(n) \,\} \end{aligned}

fo(g)f \in o(g) のとき「ffgg より真に小さいオーダーである」と読む。

OO では「ある cc について」だった箇所が oo では「すべての cc について」に変わっています。cc をいくらでも小さく取れるということは、f/gf/g00 に近づくということです。これを正確にしたのが次の命題です。

命題 3.3極限による判定法

g(n)>0g(n) > 0 が十分大きなすべての nn で成り立つとし、極限 L=limnf(n)/g(n)L = \lim_{n \to \infty} f(n)/g(n) が(++\infty を含めて)存在するとする。このとき

  1. 0L<0 \le L < \infty ならば fO(g)f \in O(g)
  2. 0<L<0 < L < \infty ならば fΘ(g)f \in \Theta(g)
  3. L=0L = 0 ならば fo(g)f \in o(g)
  4. L=L = \infty ならば fω(g)f \in \omega(g)、かつ go(f)g \in o(f)
証明(命題 3.3)
  1. L<L < \infty なので、収束の定義で ε=1\varepsilon = 1 と取ると、ある n1n_1 が存在して nn1n \ge n_1 のとき f(n)/g(n)<L+1f(n)/g(n) < L + 1 です。両辺に g(n)>0g(n) > 0 を掛けて f(n)(L+1)g(n)f(n) \le (L+1)\,g(n) を得ます。定義 3.1c=L+1>0c = L+1 > 0n0=n1n_0 = n_1 と取ればよいので fO(g)f \in O(g) です。

  2. さらに L>0L > 0 とします。ε=L/2>0\varepsilon = L/2 > 0 と取ると、ある n2n_2 が存在して nn2n \ge n_2 のとき f(n)/g(n)>LL/2=L/2f(n)/g(n) > L - L/2 = L/2、すなわち f(n)(L/2)g(n)f(n) \ge (L/2)\,g(n) です。よって c=L/2c = L/2n0=n2n_0 = n_2fΩ(g)f \in \Omega(g) となり、1 と合わせて fΘ(g)f \in \Theta(g) です。

  3. L=0L = 0 とします。任意に c>0c > 0 を与えると、収束の定義で ε=c\varepsilon = c と取れば、ある n0n_0 が存在して nn0n \ge n_0 のとき f(n)/g(n)<cf(n)/g(n) < c、すなわち f(n)cg(n)f(n) \le c\,g(n) です。cc は任意だったので 定義 3.2 により fo(g)f \in o(g) です。

  4. L=L = \infty とします。任意の c>0c > 0 に対し、発散の定義から、ある n0n_0 が存在して nn0n \ge n_0 のとき f(n)/g(n)>cf(n)/g(n) > c、すなわち f(n)cg(n)f(n) \ge c\,g(n) です。よって fω(g)f \in \omega(g) です。同じ不等式を g(n)(1/c)f(n)g(n) \le (1/c) f(n) と読み替え、c=1/cc' = 1/ccc とともに任意の正数を動くことに注意すれば go(f)g \in o(f) を得ます。

極限が存在しない場合はこの判定法は使えません。それでも OO 記法自体は意味を持ちます(演習 8.4 を参照)。

命題 3.4O 記法の計算則

f,f1,f2,g,g1,g2,hf, f_1, f_2, g, g_1, g_2, h を非負実数値関数とする。次が成り立つ。

  1. (反射律)fO(f)f \in O(f)
  2. (推移律)fO(g)f \in O(g) かつ gO(h)g \in O(h) ならば fO(h)f \in O(h)
  3. (定数倍)λ>0\lambda > 0 かつ fO(g)f \in O(g) ならば λfO(g)\lambda f \in O(g)
  4. (和)f1O(g1)f_1 \in O(g_1) かつ f2O(g2)f_2 \in O(g_2) ならば f1+f2O(max(g1,g2))f_1 + f_2 \in O(\max(g_1, g_2))。ここで max(g1,g2)\max(g_1,g_2) は各点ごとの最大値を取る関数である。
  5. (積)f1O(g1)f_1 \in O(g_1) かつ f2O(g2)f_2 \in O(g_2) ならば f1f2O(g1g2)f_1 f_2 \in O(g_1 g_2)
証明(命題 3.4)
  1. c=1c = 1n0=1n_0 = 1 とすれば、すべての n1n \ge 1f(n)1f(n)f(n) \le 1 \cdot f(n) です。

  2. 仮定より、c1>0,n1c_1 > 0, n_1 があって nn1n \ge n_1f(n)c1g(n)f(n) \le c_1 g(n)、また c2>0,n2c_2 > 0, n_2 があって nn2n \ge n_2g(n)c2h(n)g(n) \le c_2 h(n) です。nmax(n1,n2)n \ge \max(n_1, n_2) のとき、第 1 の不等式に第 2 の不等式を代入して f(n)c1g(n)c1c2h(n)f(n) \le c_1 g(n) \le c_1 c_2 h(n) を得ます(c1>0c_1 > 0 なので不等号の向きは保たれます)。c=c1c2c = c_1 c_2n0=max(n1,n2)n_0 = \max(n_1,n_2) と取ればよいのです。

  3. nn0n \ge n_0f(n)cg(n)f(n) \le c\,g(n) なら、両辺に λ>0\lambda > 0 を掛けて λf(n)λcg(n)\lambda f(n) \le \lambda c\,g(n) です。定数を λc\lambda c に取り替えます。

  4. nmax(n1,n2)n \ge \max(n_1,n_2) のとき、gimax(g1,g2)g_i \le \max(g_1,g_2) を使って

f1(n)+f2(n)c1g1(n)+c2g2(n)(c1+c2)max(g1(n),g2(n))f_1(n) + f_2(n) \le c_1 g_1(n) + c_2 g_2(n) \le (c_1 + c_2)\max(g_1(n), g_2(n))

です。定数を c1+c2c_1 + c_2 と取ります。

  1. nmax(n1,n2)n \ge \max(n_1,n_2) のとき、f1,f2,g1,g20f_1, f_2, g_1, g_2 \ge 0 なので不等式どうしを掛けてよく、f1(n)f2(n)c1c2g1(n)g2(n)f_1(n) f_2(n) \le c_1 c_2\, g_1(n) g_2(n) です。

計算則 4 は実務でいちばん使う道具です。「前処理に O(nlogn)O(n\log n)、本体に O(n2)O(n^2) かかるアルゴリズムは全体で O(n2)O(n^2)」という日常的な推論は、この規則の適用にほかなりません。

命題 3.5多項式のオーダー

d0d \ge 0 を整数、a0,,ada_0, \ldots, a_d を実数、ad>0a_d > 0 とし、p(n)=i=0dainip(n) = \sum_{i=0}^{d} a_i n^i が十分大きなすべての nn で非負であるとする。このとき pΘ(nd)p \in \Theta(n^d) である。

証明(命題 3.5)

まず上からの評価です。A=i=0daiA = \sum_{i=0}^{d} |a_i| とおくと、n1n \ge 1 のとき nindn^i \le n^d0id0 \le i \le d)なので

p(n)i=0daini(i=0dai)nd=And.p(n) \le \sum_{i=0}^{d} |a_i|\, n^i \le \Big(\sum_{i=0}^{d} |a_i|\Big) n^d = A\,n^d .

よって c=Ac = An0=1n_0 = 1 として pO(nd)p \in O(n^d) です。

次に下からの評価です。B=i=0d1aiB = \sum_{i=0}^{d-1} |a_i| とおきます(d=0d = 0 のときは B=0B = 0 で、以下は自明に成り立ちます)。n1n \ge 1

p(n)=nd(ad+i=0d1ainid),i=0d1ainidi=0d1ainidBnp(n) = n^d\Big(a_d + \sum_{i=0}^{d-1} a_i n^{i-d}\Big), \qquad \Big|\sum_{i=0}^{d-1} a_i n^{i-d}\Big| \le \sum_{i=0}^{d-1} |a_i|\, n^{i-d} \le \frac{B}{n}

が成り立ちます。最後の不等号では、id1i \le d-1 より nidn1n^{i-d} \le n^{-1} を使いました。そこで n0=2B/ad+1n_0 = \lceil 2B/a_d \rceil + 1 と取ると、nn0n \ge n_0 のとき B/nad/2B/n \le a_d/2 なので

p(n)nd(adad2)=ad2ndp(n) \ge n^d\Big(a_d - \frac{a_d}{2}\Big) = \frac{a_d}{2}\, n^d

です。c=ad/2>0c = a_d/2 > 0 として pΩ(nd)p \in \Omega(n^d) を得ます。両者を合わせて pΘ(nd)p \in \Theta(n^d) です。

注意 3.6

f(n)=O(g(n))f(n) = O(g(n)) の等号は対称ではありません。n=O(n2)n = O(n^2) は正しい主張ですが、O(n2)=nO(n^2) = n とは書きません。この等号は左から右への「\in」または「\subseteq」だと思ってください。式の途中に現れる O()O(\cdot) は「その条件を満たす何らかの関数」を表します。たとえば

i=1ni=n22+O(n)\sum_{i=1}^{n} i = \frac{n^2}{2} + O(n)

は「左辺と n2/2n^2/2 の差が O(n)O(n) に属する関数である」という意味です。この慣用は Knuth によって整理されました(参考文献 [2])。

注意 3.7

OO 記法の中では対数の底を書かなくてかまいません。底の変換公式 logan=logbn/logba\log_a n = \log_b n / \log_b a から、logan\log_a nlogbn\log_b n は正の定数倍しか違わないため、Θ(logan)=Θ(logbn)\Theta(\log_a n) = \Theta(\log_b n) が成り立つからです。ただし 2log2n=n2^{\log_2 n} = n2log10n=n0.3012^{\log_{10} n} = n^{0.301\ldots} のように、指数の肩に載せると底の違いは定数倍では済みませんOO の中の対数の底を省けるのは、対数が積の因子として現れているときだけです。

logn\log nnn よりずっと小さい」「指数関数は多項式よりずっと大きい」という感覚は、次の定理として厳密に述べられます。まず基本となる補題を証明します。

補題 4.1指数は多項式に勝つ

c>1c > 1k0k \ge 0 を実数とする。このとき実変数の極限として

limxxkcx=0\lim_{x \to \infty} \frac{x^k}{c^{\,x}} = 0

が成り立つ。

証明(補題 4.1)

まず xx が自然数 nn を動く場合を示します。c>1c > 1 より h=c1>0h = c - 1 > 0 と書けます。m=k+1m = \lceil k \rceil + 1 とおくと m>km > k です。二項定理から、nmn \ge m のとき

cn=(1+h)n(nm)hm=n(n1)(nm+1)m!hm(nm+1)mm!hmc^{\,n} = (1+h)^n \ge \binom{n}{m} h^m = \frac{n(n-1)\cdots(n-m+1)}{m!}\,h^m \ge \frac{(n-m+1)^m}{m!}\,h^m

です(各因子 n,n1,,nm+1n, n-1, \ldots, n-m+1 が最小の nm+1n-m+1 以上であることを使いました)。さらに n2mn \ge 2m なら nm+1>nmn/2n - m + 1 > n - m \ge n/2 なので

nkcnm!  nkhm(n/2)m=m!2mhm  nkm.\frac{n^k}{c^{\,n}} \le \frac{m!\; n^k}{h^m (n/2)^m} = \frac{m!\,2^m}{h^m}\; n^{\,k-m} .

m>km > k より指数 kmk - m は負であり、右辺は nn \to \infty00 に収束します。nk/cn0n^k/c^n \ge 0 なので、はさみうちにより limnnk/cn=0\lim_{n\to\infty} n^k/c^n = 0 です。

次に実変数の場合です。x1x \ge 1 に対し n=x+1n = \lfloor x \rfloor + 1 とおくと xnx \le n かつ n1xn - 1 \le x なので、k0k \ge 0c>1c > 1 より

xkcxnkcn1=cnkcn.\frac{x^k}{c^{\,x}} \le \frac{n^k}{c^{\,n-1}} = c\cdot\frac{n^k}{c^{\,n}} .

xx \to \infty のとき nn \to \infty であり、右辺は前段より 00 に収束します。したがって limxxk/cx=0\lim_{x\to\infty} x^k/c^x = 0 です。

定理 4.2増大度の階層

a>0a > 0, ε>0\varepsilon > 0, k0k \ge 0, c>1c > 1 を任意の実数とする。このとき

(log2n)ao(nε),nko(cn),cno(n!)(\log_2 n)^a \in o(n^{\varepsilon}), \qquad n^k \in o(c^{\,n}), \qquad c^{\,n} \in o(n!)

が成り立つ。とくに ε=1\varepsilon = 1a=1a = 1c=2c = 2 と取れば、log2n\log_2 nnn2n2^nn!n! はこの順に真に大きなオーダーである。

証明(定理 4.2)

第 1 の主張。 t=log2nt = \log_2 n とおくと n=2tn = 2^t であり、nn \to \infty のとき tt \to \infty です。このとき

(log2n)anε=ta2εt=ta(2ε)t.\frac{(\log_2 n)^a}{n^{\varepsilon}} = \frac{t^a}{2^{\varepsilon t}} = \frac{t^a}{(2^{\varepsilon})^{t}} .

ε>0\varepsilon > 0 より 2ε>12^{\varepsilon} > 1 なので、補題 4.1c=2εc = 2^{\varepsilon}k=ak = ax=tx = t として適用すると、この比は 00 に収束します。命題 3.3 の 3 より (log2n)ao(nε)(\log_2 n)^a \in o(n^{\varepsilon}) です。

第 2 の主張。 補題 4.1 をそのまま x=nx = n に適用すれば nk/cn0n^k/c^n \to 0 であり、再び 命題 3.3 の 3 から nko(cn)n^k \in o(c^n) です。

第 3 の主張。 m=2cm = \lceil 2c \rceil とおきます。n>mn > m のとき

cnn!=cmm!j=m+1ncj\frac{c^{\,n}}{n!} = \frac{c^{\,m}}{m!}\prod_{j=m+1}^{n} \frac{c}{j}

と分解できます。jm+1>2cj \ge m+1 > 2c より各因子は c/j<1/2c/j < 1/2 なので、積は (1/2)nm(1/2)^{\,n-m} 以下です。よって

0cnn!cmm!(12)nm0 \le \frac{c^{\,n}}{n!} \le \frac{c^{\,m}}{m!}\left(\frac{1}{2}\right)^{n-m}

であり、右辺は nn \to \infty00 に収束します(ccmmnn に依存しない定数です)。はさみうちにより cn/n!0c^n/n! \to 0 となり、cno(n!)c^n \in o(n!) です。

第 1 の主張は、どんなに小さな ε>0\varepsilon > 0 を取っても、(logn)100(\log n)^{100} より nεn^{\varepsilon} のほうが最終的には大きいと言っています。対数はそれほど遅く増えます。第 2 の主張は、n1000n^{1000} より 1.001n1.001^n のほうが最終的には大きいと言っています。指数はそれほど速く増えます。この二つが、次節で見る劇的な差の源です。

1816243202550751002ⁿn log nnlog nn(入力サイズ)基本演算の回数
代表的な増大度(縦軸は 100 で打ち切り)

各クラスの性格をつかむ最も手軽な方法は、「入力サイズを 2 倍にしたら実行時間がどうなるか」を見ることです。

命題 5.1入力を 2 倍にしたときの比

α>0\alpha > 0β\beta を定数、k>0k > 0 を実数とする。次が成り立つ。

  1. T(n)=αT(n) = \alpha ならば T(2n)/T(n)=1T(2n)/T(n) = 1
  2. T(n)=αlog2n+βT(n) = \alpha \log_2 n + \beta ならば T(2n)T(n)=αT(2n) - T(n) = \alpha(比ではなく差が一定)。
  3. T(n)=αnkT(n) = \alpha n^{k} ならば T(2n)/T(n)=2kT(2n)/T(n) = 2^{k}
  4. T(n)=αnlog2nT(n) = \alpha n \log_2 n ならば T(2n)/T(n)=2(1+1log2n)T(2n)/T(n) = 2\left(1 + \dfrac{1}{\log_2 n}\right) であり、nn \to \infty22 に収束する。
  5. T(n)=α2nT(n) = \alpha\, 2^{n} ならば T(2n)/T(n)=2nT(2n)/T(n) = 2^{n}
証明(命題 5.1)

いずれも代入して計算します。

  1. T(2n)/T(n)=α/α=1T(2n)/T(n) = \alpha/\alpha = 1
  2. T(2n)T(n)=α(log22nlog2n)+(ββ)=αlog22=αT(2n) - T(n) = \alpha(\log_2 2n - \log_2 n) + (\beta - \beta) = \alpha \log_2 2 = \alpha
  3. T(2n)/T(n)=α(2n)k/(αnk)=2knk/nk=2kT(2n)/T(n) = \alpha (2n)^k / (\alpha n^k) = 2^k n^k / n^k = 2^k
  4. T(2n)/T(n)=α2nlog22nαnlog2n=2log2n+1log2n=2(1+1log2n)T(2n)/T(n) = \dfrac{\alpha \cdot 2n \log_2 2n}{\alpha\, n \log_2 n} = 2\cdot\dfrac{\log_2 n + 1}{\log_2 n} = 2\left(1 + \dfrac{1}{\log_2 n}\right)nn \to \infty1/log2n01/\log_2 n \to 0 なので比は 22 に収束します。
  5. T(2n)/T(n)=α22n/(α2n)=22nn=2nT(2n)/T(n) = \alpha 2^{2n}/(\alpha 2^{n}) = 2^{2n-n} = 2^{n}

主張 5 が指数時間の恐ろしさを端的に表しています。n=40n = 40 の問題を解いたあと n=80n = 80 に進むと、時間は 2401.1×10122^{40} \approx 1.1\times 10^{12} 倍になります。

注意 5.2

命題 5.1 は「ちょうど」その形の関数についての主張であり、TΘ(n)T \in \Theta(n) という条件だけからは T(2n)/T(n)2T(2n)/T(n) \to 2 は導けません。反例として T(n)=n(2+sinn)T(n) = n\,(2 + \sin n) を取ると、12+sinn31 \le 2+\sin n \le 3 より TΘ(n)T \in \Theta(n) ですが、T(2n)/T(n)=2(2+sin2n)/(2+sinn)T(2n)/T(n) = 2(2+\sin 2n)/(2+\sin n)2/32/3 倍から 66 倍のあいだを振動し、収束しません。Θ\Theta は定数倍を捨てているので、比の極限までは決めないのです。

例 5.3実際の演算回数と実行時間

1 秒間に 10910^9 回の基本演算を行う計算機を仮定します。演算回数は次のとおりです。

nnlog2n\log_2 nnnnlog2nn\log_2 nn2n^22n2^n
10103.33.31010333310210^{2}1.0×1031.0\times10^{3}
1001006.66.610010066466410410^{4}1.3×10301.3\times10^{30}
10310^{3}10.010.010310^{3}1.0×1041.0\times10^{4}10610^{6}天文学的
10610^{6}19.919.910610^{6}2.0×1072.0\times10^{7}101210^{12}天文学的
10910^{9}29.929.910910^{9}3.0×10103.0\times10^{10}101810^{18}天文学的

これを時間に直します。

T(n)T(n)n=106n = 10^{6}n=109n = 10^{9}
nn0.0010.00111
nlog2nn\log_2 n0.020.023030
n2n^217173232

nlog2nn\log_2 n の欄を見てください。n=109n = 10^9 でも log2n\log_2 n3030 にすぎないので、nlognn\log nnn の高々 30 倍です。現実的な入力サイズの範囲では、Θ(nlogn)\Theta(n\log n)Θ(n)\Theta(n) とほとんど変わりません。 これが、比較ソートや高速フーリエ変換のような Θ(nlogn)\Theta(n\log n) アルゴリズムが「実質的に線形」と扱われる理由です。同じことは Θ(logn)\Theta(\log n) にも当てはまります。nn1010 から 10910^9 へと 1 億倍になっても、log2n\log_2 n3.33.3 から 29.929.9 へ 9 倍にしかなりません。二分探索が要素数によらず一瞬で終わるように見えるのはこのためです(定理 3.3[探索アルゴリズム]探索アルゴリズム を参照)。

指数の側も見ておきます。2501.1×10152^{50} \approx 1.1\times10^{15} 回は約 13 日、21001.3×10302^{100} \approx 1.3\times10^{30} 回は約 4×10134\times10^{13} 年で、これは宇宙の年齢(約 1.4×10101.4\times10^{10} 年)のおよそ 2900 倍です。階乗はさらに速く、20!2.4×101820! \approx 2.4\times10^{18} は約 77 年に相当します。

指数時間の本質的な困難は、次の比較で最もはっきりします。

例 5.4計算機が 1000 倍速くなったら

1 秒間に扱える最大の nn を、10910^9 演算/秒の計算機と 101210^{12} 演算/秒の計算機で比べます。

T(n)T(n)10910^{9} 演算/秒101210^{12} 演算/秒変化
nn10910^{9}101210^{12}10001000
nlog2nn\log_2 n4.0×1074.0\times10^{7}2.9×10102.9\times10^{10}730730
n2n^23.2×1043.2\times10^{4}10610^{6}31.631.6
n3n^310310^{3}10410^{4}1010
2n2^n29293939+10+10

最下行を確かめます。2n=t2^{n} = t を解くと n=log2tn = \log_2 t なので、tt10001000 倍になったときの nn の増加は log21000=9.97\log_2 1000 = 9.97、つまり約 10 です。これは計算機の速度によらない一般的な事実であり、T(n)=αcnT(n) = \alpha\,c^{n} の形なら増加分は logc1000\log_c 1000 です。

n2n^2 の行では 1000=31.6\sqrt{1000} = 31.6 倍、n3n^3 の行では 10001/3=101000^{1/3} = 10 倍というように、指数の逆数乗だけ改善します。一般に T(n)=αnkT(n) = \alpha n^{k} なら、計算機が ss 倍速くなったとき扱える nns1/ks^{1/k} 倍になります。

結論は明快です。多項式時間なら計算機の進歩が効きますが、指数時間ではほとんど効きません。 指数時間の壁を破るには、より良いアルゴリズムを見つけるしかないのです。

例 5.5総当りから動的計画法へ

nn 個の正整数 w1,,wnw_1, \ldots, w_n と目標値 WW が与えられ、和がちょうど WW になる部分集合があるかを判定する問題(部分和問題)を考えます。すべての部分集合を列挙する総当りは 2n2^n 通りを調べるので Θ(2nn)\Theta(2^n \cdot n) です。n=40n = 40 なら 240×404.4×10132^{40} \times 40 \approx 4.4\times10^{13} 演算、10910^9 演算/秒の計算機で約 12 時間かかります。

一方、b[i][w]b[i][w] を「最初の ii 個から和 ww が作れるか」とする表を埋める動的計画法は Θ(nW)\Theta(nW) で済みます。n=40n = 40W=104W = 10^4 なら 4×1054\times10^5 演算、0.00040.0004 秒です。3000 万倍以上の高速化ですが、これは計算機を替えたのではなく、同じ部分和を何度も数え直すのをやめただけです(同じ形の漸化式とその計算量は 系 6.4[動的計画法] で扱います。動的計画法 を参照)。

なお Θ(nW)\Theta(nW) は入力サイズの多項式ではありません。WW を表すのに必要なのは log2W\log_2 W ビットなので、WW は入力サイズについて指数的に大きくなりえます。このような計算量を擬多項式時間と呼びます。

同じ操作でも、データ構造を変えれば計算量が変わります。代表的な構造の最悪計算量を並べます(nn は格納された要素数)。

操作未整列の配列整列済み配列連結リスト平衡二分探索木ハッシュ表
値の検索Θ(n)\Theta(n)Θ(logn)\Theta(\log n)Θ(n)\Theta(n)Θ(logn)\Theta(\log n)平均 Θ(1)\Theta(1) / 最悪 Θ(n)\Theta(n)
挿入Θ(1)\Theta(1)(末尾)Θ(n)\Theta(n)Θ(1)\Theta(1)(位置既知)Θ(logn)\Theta(\log n)平均 Θ(1)\Theta(1)
削除Θ(n)\Theta(n)(検索込み)Θ(n)\Theta(n)Θ(1)\Theta(1)(位置既知)Θ(logn)\Theta(\log n)平均 Θ(1)\Theta(1)
最小値の取得Θ(n)\Theta(n)Θ(1)\Theta(1)Θ(n)\Theta(n)Θ(logn)\Theta(\log n)Θ(n)\Theta(n)

万能な構造はありません。整列済み配列は検索が速い代わりに挿入で全体をずらす必要があり、連結リストは挿入が速い代わりに kk 番目の要素に到達するのに Θ(k)\Theta(k) かかります(命題 4.2[基本的なデータ構造])。何を速くしたいかを決めてから構造を選ぶことになります。各構造の定義と、これらの計算量の証明は 基本的なデータ構造 で扱います。

注意 5.6

表の「未整列の配列への末尾挿入 Θ(1)\Theta(1)」は、容量が足りているときの話です。容量が尽きたら新しい領域を確保して全要素をコピーするので、その回だけは Θ(n)\Theta(n) かかります。しかし容量を尽きるたびに 2 倍にする戦略を取ると、容量 11 から始めて nn 回挿入するまでにコピーが起きるのは要素数が 1,2,4,,2k1, 2, 4, \ldots, 2^{k}2k<n2^{k} < n)のときだけで、総コピー回数は

1+2+4++2k=2k+11<2n1 + 2 + 4 + \cdots + 2^{k} = 2^{k+1} - 1 < 2n

です。nn 回の挿入の総コストが O(n)O(n) なので、1 回あたりの平均は O(1)O(1) です。このように操作列全体で均した計算量を償却計算量と呼びます(この評価を定理の形で述べたものが 定理 3.3[基本的なデータ構造] です)。個々の操作の最悪計算量とは別の概念なので、区別してください。

6. 分割統治:漸化式からオーダーを出す

Section titled “6. 分割統治:漸化式からオーダーを出す”

再帰的なアルゴリズムの計算量は漸化式として現れます。マージソートなら、長さ nn の配列を半分ずつに分けて再帰し、Θ(n)\Theta(n) 時間で併合するので T(n)=2T(n/2)+Θ(n)T(n) = 2\,T(n/2) + \Theta(n) です(定理 4.2[ソートアルゴリズム])。この形の漸化式は、次の定理で一気に解けます。

定理 6.1マスター定理

a1a \ge 1b>1b > 1 を実数、d>0d > 0 を定数とし、ffbb のべき乗の上で定義された正の値をとる関数とする。関数 TT

T(1)=d,T(n)=aT(n/b)+f(n)(n=bk, k1)T(1) = d, \qquad T(n) = a\,T(n/b) + f(n) \quad (n = b^{k},\ k \ge 1)

を満たすとする。nnbb のべき乗の上を動くものとして、次が成り立つ。

  1. ある ε>0\varepsilon > 0 について f(n)=O ⁣(nlogbaε)f(n) = O\!\left(n^{\log_b a - \varepsilon}\right) ならば、T(n)=Θ ⁣(nlogba)T(n) = \Theta\!\left(n^{\log_b a}\right)
  2. f(n)=Θ ⁣(nlogba)f(n) = \Theta\!\left(n^{\log_b a}\right) ならば、T(n)=Θ ⁣(nlogbalogn)T(n) = \Theta\!\left(n^{\log_b a}\log n\right)
  3. ある ε>0\varepsilon > 0 について f(n)=Ω ⁣(nlogba+ε)f(n) = \Omega\!\left(n^{\log_b a + \varepsilon}\right) であり、かつある定数 0<c<10 < c < 1 が存在してすべての k1k \ge 1af(bk1)cf(bk)a\,f(b^{k-1}) \le c\,f(b^{k})(正則条件)が成り立つならば、T(n)=Θ(f(n))T(n) = \Theta(f(n))
証明(定理 6.1)

n=bkn = b^{k} とします。漸化式を kk 回展開すると

T(n)=akT(1)+j=0k1ajf ⁣(nbj)T(n) = a^{k} T(1) + \sum_{j=0}^{k-1} a^{j} f\!\left(\frac{n}{b^{j}}\right)

です(kk についての帰納法:k=0k=0 では両辺 T(1)T(1) で一致し、kk で成り立つとして T(bk+1)=aT(bk)+f(bk+1)T(b^{k+1}) = aT(b^{k}) + f(b^{k+1}) に代入すれば k+1k+1 でも成り立ちます)。ここで

ak=alogbn=(blogba)logbn=(blogbn)logba=nlogbaa^{k} = a^{\log_b n} = \left(b^{\log_b a}\right)^{\log_b n} = \left(b^{\log_b n}\right)^{\log_b a} = n^{\log_b a}

なので、第 1 項は dnlogbad\,n^{\log_b a} です。f>0f > 0 より、つねに T(n)dnlogbaT(n) \ge d\,n^{\log_b a} が成り立ちます。あとは和 Σ=j=0k1ajf(n/bj)\Sigma = \sum_{j=0}^{k-1} a^{j} f(n/b^{j}) を評価します。

場合 1。 仮定より、ある C>0C > 0 があって f(m)Cmlogbaεf(m) \le C\,m^{\log_b a - \varepsilon}mm は十分大きい bb のべき乗)です。すると

ΣCj=0k1aj(nbj)logbaε=Cnlogbaεj=0k1ajbj(logbaε).\Sigma \le C \sum_{j=0}^{k-1} a^{j}\left(\frac{n}{b^{j}}\right)^{\log_b a - \varepsilon} = C\,n^{\log_b a - \varepsilon} \sum_{j=0}^{k-1} a^{j}\, b^{-j(\log_b a - \varepsilon)} .

ここで bjlogba=ajb^{-j\log_b a} = a^{-j} なので ajbj(logbaε)=(bε)ja^{j} b^{-j(\log_b a - \varepsilon)} = (b^{\varepsilon})^{j} となり、等比級数の公式から

j=0k1(bε)j=bεk1bε1<nεbε1\sum_{j=0}^{k-1} (b^{\varepsilon})^{j} = \frac{b^{\varepsilon k} - 1}{b^{\varepsilon} - 1} < \frac{n^{\varepsilon}}{b^{\varepsilon} - 1}

です(bεk=(bk)ε=nεb^{\varepsilon k} = (b^{k})^{\varepsilon} = n^{\varepsilon} を使いました)。よって Σ<Cbε1nlogba\Sigma < \dfrac{C}{b^{\varepsilon}-1}\,n^{\log_b a} であり、T(n)=O(nlogba)T(n) = O(n^{\log_b a}) です。下からの評価は上で述べた T(n)dnlogbaT(n) \ge d\,n^{\log_b a} なので、合わせて T(n)=Θ(nlogba)T(n) = \Theta(n^{\log_b a}) を得ます。

場合 2。 仮定より c1mlogbaf(m)c2mlogbac_1 m^{\log_b a} \le f(m) \le c_2 m^{\log_b a} となる c1,c2>0c_1, c_2 > 0 があります。aj(n/bj)logba=ajnlogbaaj=nlogbaa^{j}(n/b^{j})^{\log_b a} = a^{j} n^{\log_b a} a^{-j} = n^{\log_b a} なので、和の各項は c1nlogbac_1 n^{\log_b a} 以上 c2nlogbac_2 n^{\log_b a} 以下です。項数は k=logbnk = \log_b n なので

c1nlogbalogbnΣc2nlogbalogbnc_1\,n^{\log_b a} \log_b n \le \Sigma \le c_2\,n^{\log_b a}\log_b n

です。logbn\log_b nlogn\log n は正の定数倍しか違わない(注意 3.7)ので、Σ=Θ(nlogbalogn)\Sigma = \Theta(n^{\log_b a}\log n) です。第 1 項 dnlogbad\,n^{\log_b a} はこれに吸収されるので T(n)=Θ(nlogbalogn)T(n) = \Theta(n^{\log_b a}\log n) です。

場合 3。 正則条件から、jj についての帰納法で ajf(n/bj)cjf(n)a^{j} f(n/b^{j}) \le c^{\,j} f(n) が示せます。実際 j=0j = 0 では等号です。jj で成り立つとすると、正則条件を n/bjn/b^{j} に適用して af(n/bj+1)cf(n/bj)a f(n/b^{j+1}) \le c f(n/b^{j}) なので

aj+1f(n/bj+1)=ajaf(n/bj+1)ajcf(n/bj)ccjf(n)=cj+1f(n)a^{j+1} f(n/b^{j+1}) = a^{j}\cdot a f(n/b^{j+1}) \le a^{j}\, c\, f(n/b^{j}) \le c\cdot c^{\,j} f(n) = c^{\,j+1} f(n)

となります。したがって 0<c<10 < c < 1 より

Σf(n)j=0k1cj<f(n)1c.\Sigma \le f(n) \sum_{j=0}^{k-1} c^{\,j} < \frac{f(n)}{1-c} .

また仮定 f(n)=Ω(nlogba+ε)f(n) = \Omega(n^{\log_b a + \varepsilon}) から、ある c3>0c_3 > 0 と十分大きな nnnlogbaf(n)c3nεf(n)c3n^{\log_b a} \le \dfrac{f(n)}{c_3\,n^{\varepsilon}} \le \dfrac{f(n)}{c_3} が成り立つので、第 1 項も O(f(n))O(f(n)) です。よって T(n)=O(f(n))T(n) = O(f(n)) です。一方、展開式で j=0j = 0 の項を取れば T(n)f(n)T(n) \ge f(n) なので T(n)=Ω(f(n))T(n) = \Omega(f(n)) であり、T(n)=Θ(f(n))T(n) = \Theta(f(n)) を得ます。

注意 6.2

実際のアルゴリズムでは n/bn/b が整数とは限らず、漸化式は T(n)=aT(n/b)+f(n)T(n) = a\,T(\lfloor n/b\rfloor) + f(n) の形になります。この床関数つきの漸化式でも 定理 6.1 と同じ結論が成り立つことが知られており、証明は Cormen ほか(参考文献 [1])の第 4 章にあります。また同書では正則条件を「十分大きなすべての nn で」という弱い形で述べています。この弱い形でも、条件が破れる項は高々 logbn0+1\log_b n_0 + 1 個で、それぞれ O(nlogba)=O(f(n))O(n^{\log_b a}) = O(f(n)) なので結論は変わりません。マスター定理が使えない漸化式への対処法は Appendix で扱います。

例 6.3マスター定理の適用

(a) マージソート。 T(n)=2T(n/2)+Θ(n)T(n) = 2\,T(n/2) + \Theta(n) では a=2a = 2, b=2b = 2, f(n)=Θ(n)f(n) = \Theta(n) です。logba=log22=1\log_b a = \log_2 2 = 1 なので f(n)=Θ(n1)=Θ(nlogba)f(n) = \Theta(n^{1}) = \Theta(n^{\log_b a}) となり、場合 2 に当てはまります。よって T(n)=Θ(nlogn)T(n) = \Theta(n\log n) です。

(b) 二分探索。 T(n)=T(n/2)+Θ(1)T(n) = T(n/2) + \Theta(1) では a=1a = 1, b=2b = 2, f(n)=Θ(1)f(n) = \Theta(1) です。log21=0\log_2 1 = 0 なので nlogba=n0=1n^{\log_b a} = n^{0} = 1 であり、f(n)=Θ(1)=Θ(nlogba)f(n) = \Theta(1) = \Theta(n^{\log_b a}) です。ふたたび場合 2 に当てはまり、T(n)=Θ(n0logn)=Θ(logn)T(n) = \Theta(n^{0}\log n) = \Theta(\log n) です。

(c) Strassen の行列乗算。 T(n)=7T(n/2)+Θ(n2)T(n) = 7\,T(n/2) + \Theta(n^{2}) では a=7a = 7, b=2b = 2, f(n)=Θ(n2)f(n) = \Theta(n^{2}) です。log27=2.8073\log_2 7 = 2.8073\ldots なので、ε=0.5\varepsilon = 0.5 と取れば nlog270.5=n2.307n^{\log_2 7 - 0.5} = n^{2.307\ldots} であり、n2=O(n2.307)n^{2} = O(n^{2.307\ldots}) が成り立ちます。よって場合 1 に当てはまり、T(n)=Θ(nlog27)T(n) = \Theta(n^{\log_2 7})、とくに T(n)=O(n2.808)T(n) = O(n^{2.808}) です。素朴な三重ループの Θ(n3)\Theta(n^{3}) より真に小さいオーダーです。

(d) 場合 3 の例。 T(n)=2T(n/2)+n2T(n) = 2\,T(n/2) + n^{2} では log22=1\log_2 2 = 1 で、ε=1\varepsilon = 1 として n2=Ω(n1+1)n^{2} = \Omega(n^{1+1}) です。正則条件は 2(n/2)2=n2/2cn22\,(n/2)^{2} = n^{2}/2 \le c\,n^{2}c=1/2<1c = 1/2 < 1 で成り立つので満たされます。よって T(n)=Θ(n2)T(n) = \Theta(n^{2}) で、再帰の最上段のコストだけで全体が決まります。

ここまで見てきた Θ(n)\Theta(n), Θ(nlogn)\Theta(n\log n), Θ(n2)\Theta(n^2), Θ(n3)\Theta(n^3) はいずれも「nn の多項式で抑えられる」という共通の性質を持ち、Θ(2n)\Theta(2^n), Θ(n!)\Theta(n!) は持ちません。例 5.4 が示したとおり、この境界は計算機の性能向上で動かせません。そこで次の定義が立てられます。

定義 7.1多項式時間アルゴリズム

アルゴリズム AA多項式時間であるとは、ある定数 kk が存在して TA(n)=O(nk)T_A(n) = O(n^{k}) が成り立つことをいう。

多項式時間を「効率的」の定義とする立場は Cobham と Edmonds に遡ります。この定義には n100n^{100} も含まれてしまうという難点がありますが、次の 2 点で強い正当性を持ちます。第一に、多項式の集合は加法・乗法・合成について閉じているので、多項式時間アルゴリズムを部品として組み合わせても多項式時間のままです。第二に、この区別は計算モデルの細部に依存しません。RAM モデルとチューリング機械のあいだで計算量は多項式のずれしか生じないため、「多項式時間で解けるか」という問いはモデルを替えても答えが変わらないのです。

多項式時間で解ける判定問題の全体を P\mathrm{P}、答えが「はい」のときにその証拠を多項式時間で検証できる判定問題の全体を NP\mathrm{NP}定義 4.1[P≠NP 予想とは何か])と書きます。PNP\mathrm{P} \subseteq \mathrm{NP} は定義からすぐ従いますが、逆向きの包含が成り立つかは 1971 年の問題提起以来未解決です。これが P≠NP 予想であり、部分和問題や巡回セールスマン問題を含む数千の問題が「PNP\mathrm{P} \ne \mathrm{NP} ならば多項式時間アルゴリズムを持たない」という形で結びついています。詳しくは P≠NP予想とは何か を参照してください。

計算量の記法を学ぶ意味はここにあります。OO 記法は個々のプログラムの速さを測る道具であると同時に、「何が計算できて何が計算できないか」を論じるための共通言語でもあるのです。

演習 8.1

f(n)=3n2+5nlog2n+100f(n) = 3n^{2} + 5n\log_2 n + 100 とする。fΘ(n2)f \in \Theta(n^{2}) であることを、定義 3.1 の定数 ccn0n_0 を具体的に与えて示してください。

解答

上からの評価。 n2n \ge 2 のとき log2nn\log_2 n \le n なので 5nlog2n5n25n\log_2 n \le 5n^{2} です。また n10n \ge 10 のとき 100n2100 \le n^{2} です。よって n10n \ge 10 のとき

f(n)3n2+5n2+n2=9n2f(n) \le 3n^{2} + 5n^{2} + n^{2} = 9n^{2}

となり、c2=9c_2 = 9n0=10n_0 = 10 として fO(n2)f \in O(n^{2}) です。

下からの評価。 5nlog2n05n\log_2 n \ge 0n1n \ge 1)と 100>0100 > 0 より、すべての n1n \ge 1f(n)3n2f(n) \ge 3n^{2} です。よって c1=3c_1 = 3n0=1n_0 = 1 として fΩ(n2)f \in \Omega(n^{2}) です。

両者を合わせ、c1=3c_1 = 3, c2=9c_2 = 9, n0=10n_0 = 10fΘ(n2)f \in \Theta(n^{2}) です。なお log2nn\log_2 n \le nn1n \ge 1)は、定理 4.2 の第 1 主張から log2no(n)\log_2 n \in o(n) が従うことでも保証されますが、ここでは n2n \ge 22nn2^{n} \ge nnn についての帰納法:22=422^{2} = 4 \ge 2 で、2nn2^{n} \ge n なら 2n+1=22n2nn+12^{n+1} = 2\cdot 2^{n} \ge 2n \ge n+1)から直接得られます。

演習 8.2標準

log2(n!)Θ(nlogn)\log_2(n!) \in \Theta(n\log n) を示してください。Stirling の公式は使わないこと。

解答

上からの評価。 n!=i=1nii=1nn=nnn! = \prod_{i=1}^{n} i \le \prod_{i=1}^{n} n = n^{n} なので、両辺の log2\log_2 を取って(log2\log_2 は単調増加)

log2(n!)log2(nn)=nlog2n.\log_2(n!) \le \log_2(n^{n}) = n\log_2 n .

よって c=1c = 1, n0=1n_0 = 1log2(n!)O(nlogn)\log_2(n!) \in O(n\log n) です。

下からの評価。 n2n \ge 2 とし、積のうち大きいほうの半分だけを残します。in/2i \ge \lceil n/2\rceil を満たす ii は少なくとも n/2n/2 個あり、そのそれぞれが n/2n/2 以上なので

n!i=n/2ni(n2)n/2.n! \ge \prod_{i=\lceil n/2\rceil}^{n} i \ge \left(\frac{n}{2}\right)^{n/2} .

log2\log_2 を取ると

log2(n!)n2(log2n1).\log_2(n!) \ge \frac{n}{2}\left(\log_2 n - 1\right) .

n4n \ge 4 のとき log2n2\log_2 n \ge 2 なので log2n1log2n12log2n=12log2n\log_2 n - 1 \ge \log_2 n - \frac{1}{2}\log_2 n = \frac{1}{2}\log_2 n です。よって

log2(n!)n4log2n(n4)\log_2(n!) \ge \frac{n}{4}\log_2 n \qquad (n \ge 4)

であり、c=1/4c = 1/4, n0=4n_0 = 4log2(n!)Ω(nlogn)\log_2(n!) \in \Omega(n\log n) です。合わせて Θ(nlogn)\Theta(n\log n) を得ます。

この評価は、比較ソートの最悪比較回数が Ω(nlogn)\Omega(n\log n) であるという下界(定理 6.1[ソートアルゴリズム])の証明に使われます。

演習 8.3標準

漸化式 T(1)=1T(1) = 1, T(n)=3T(n/4)+nlog2nT(n) = 3\,T(n/4) + n\log_2 nnn44 のべき乗)を解いてください。定理 6.1 のどの場合に当てはまるか、正則条件が満たされるかを明示すること。

解答

a=3a = 3, b=4b = 4, f(n)=nlog2nf(n) = n\log_2 n です。まず logba=log43=0.7924\log_b a = \log_4 3 = 0.7924\ldots を計算します。

どの場合か。 ε=0.2\varepsilon = 0.2 と取ると log43+ε=0.9924<1\log_4 3 + \varepsilon = 0.9924\ldots < 1 です。n2n \ge 2 のとき log2n1\log_2 n \ge 1 なので f(n)=nlog2nnn0.9925f(n) = n\log_2 n \ge n \ge n^{0.9925} であり、f(n)=Ω(nlog43+ε)f(n) = \Omega(n^{\log_4 3 + \varepsilon}) が成り立ちます。よって場合 3 の第 1 の条件を満たします。

正則条件。 n=4kn = 4^{k}k1k \ge 1)に対し

af(n/b)=3n4log2n4=34n(log2n2)34nlog2n=34f(n)a\,f(n/b) = 3\cdot\frac{n}{4}\log_2\frac{n}{4} = \frac{3}{4}\,n\left(\log_2 n - 2\right) \le \frac{3}{4}\,n\log_2 n = \frac{3}{4}\,f(n)

です。c=3/4<1c = 3/4 < 1 と取れるので正則条件は満たされます(log2n2log2n\log_2 n - 2 \le \log_2 n20-2 \le 0 から従います)。

結論。 定理 6.1 の場合 3 より T(n)=Θ(f(n))=Θ(nlogn)T(n) = \Theta(f(n)) = \Theta(n\log n) です。再帰の分岐数 33 が縮小率 44 に負けているため、最上段のコストだけで全体が決まります。

演習 8.4

fO(g)f \notin O(g) かつ gO(f)g \notin O(f) を満たす非負関数の組 f,gf, g を具体的に構成し、証明してください。この例は 命題 3.3 の仮定(極限の存在)が本質的であることを示します。

解答

構成。 N\mathbb{N} 上の関数を

f(n)={n2(n が偶数)n(n が奇数)g(n)={n(n が偶数)n2(n が奇数)f(n) = \begin{cases} n^{2} & (n \text{ が偶数}) \\ n & (n \text{ が奇数}) \end{cases} \qquad g(n) = \begin{cases} n & (n \text{ が偶数}) \\ n^{2} & (n \text{ が奇数}) \end{cases}

と定めます。どちらも非負です。

fO(g)f \notin O(g) の証明。 fO(g)f \in O(g) と仮定します。すると、ある c>0c > 0n0n_0 があって、nn0n \ge n_0 のすべての nnf(n)cg(n)f(n) \le c\,g(n) です。ここで nn として max(n0,c+1)\max(n_0, \lceil c \rceil + 1) 以上の偶数を一つ取ります(そのような偶数は存在します)。この nn では f(n)=n2f(n) = n^{2}g(n)=ng(n) = n なので、不等式は n2cnn^{2} \le c\,n、すなわち ncn \le c となります。ところが nc+1>cn \ge \lceil c\rceil + 1 > c なので矛盾です。よって fO(g)f \notin O(g) です。

gO(f)g \notin O(f) の証明。 まったく同じ議論を、偶数のかわりに奇数で行います。gO(f)g \in O(f) と仮定して c,n0c, n_0 を取り、max(n0,c+1)\max(n_0, \lceil c\rceil + 1) 以上の奇数 nn を取ると g(n)=n2g(n) = n^{2}f(n)=nf(n) = n なので ncn \le c となって矛盾します。

極限との関係。 f(n)/g(n)f(n)/g(n) は偶数 nnnn、奇数 nn1/n1/n なので、nn \to \infty で振動し極限を持ちません。命題 3.3 は極限の存在を仮定していたので、この例には適用できません。OO 記法による大小関係は全順序ではない、というのがこの演習の教訓です。

  1. T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, 4th ed., MIT Press, 2022 — 第 3 章(漸近記法)、第 4 章(分割統治と漸化式、マスター定理の床関数つきの形)。
  2. D. E. Knuth, “Big Omicron and big Omega and big Theta”, ACM SIGACT News 8 (1976), 18–24. DOI: 10.1145/1008328.1008329OO, Ω\Omega, Θ\Theta の用法を計算機科学向けに整理した論文。
  3. D. E. Knuth, The Art of Computer Programming, Volume 1: Fundamental Algorithms, 3rd ed., Addison-Wesley, 1997 — 1.2.11 節(漸近的表現)。
  4. R. L. Graham, D. E. Knuth, O. Patashnik, Concrete Mathematics, 2nd ed., Addison-Wesley, 1994 — 第 9 章(Asymptotics)。漸近展開の技法を詳しく扱っています。
  5. J. Kleinberg, É. Tardos, Algorithm Design, Addison-Wesley, 2005 — 第 2 章(アルゴリズム解析の基礎と代表的な計算量クラス)。
  6. M. R. Garey, D. S. Johnson, Computers and Intractability: A Guide to the Theory of NP-Completeness, W. H. Freeman, 1979 — 第 1 章。多項式時間と指数時間の差を計算機の高速化と対比する表があります。

Appendix: マスター定理が使えないとき

Section titled “Appendix: マスター定理が使えないとき”

定理 6.1nnbb のべき乗である場合に述べました。実際の漸化式は床関数を含み、また分割の形がマスター定理の枠に収まらないこともあります。そのようなときは置換法、すなわち答えを予想して帰納法で検証する方法が使えます。マージソートの正確な漸化式で見ます。

T(1)=1T(1) = 1n2n \ge 2 に対し T(n)=2T(n/2)+nT(n) = 2\,T(\lfloor n/2\rfloor) + n と定めます(実際のマージソートは n/2\lfloor n/2 \rfloorn/2\lceil n/2\rceil に分けますが、ここでは技法を見るため簡略化した形を扱います)。

上からの評価。 すべての n2n \ge 2T(n)2nlog2nT(n) \le 2n\log_2 n が成り立つことを、nn についての強い帰納法で示します。

  • n=2n = 2T(2)=2T(1)+2=4T(2) = 2T(1) + 2 = 4 であり、22log22=42\cdot 2\log_2 2 = 4 なので T(2)4T(2) \le 4 が成り立ちます。
  • n=3n = 3T(3)=2T(1)+3=5T(3) = 2T(1) + 3 = 5 であり、23log23=9.502\cdot 3\log_2 3 = 9.50\ldots なので成り立ちます。
  • n4n \ge 4:このとき n/22\lfloor n/2\rfloor \ge 2 かつ n/2<n\lfloor n/2 \rfloor < n なので帰納法の仮定が使えて、T(n/2)2n/2log2n/2T(\lfloor n/2\rfloor) \le 2\lfloor n/2\rfloor \log_2 \lfloor n/2\rfloor です。n/2n/2\lfloor n/2\rfloor \le n/2log2\log_2 の単調性から
T(n)22n2log2n2+n=2n(log2n1)+n=2nlog2nn2nlog2nT(n) \le 2\cdot 2\cdot\frac{n}{2}\log_2\frac{n}{2} + n = 2n(\log_2 n - 1) + n = 2n\log_2 n - n \le 2n\log_2 n

です。最後の不等号は n>0n > 0 から従います。

よって T(n)=O(nlogn)T(n) = O(n\log n) です。n-n という余りが残った点が重要で、帰納法が「回った」のはこの余裕があったからです。もし T(n)cnlog2nT(n) \le c\,n\log_2 nc=1c = 1 で示そうとすると余りが 00 以下にならず、帰納が閉じません。

下からの評価。 まず TT が単調非減少であることを示します。nn についての強い帰納法で T(n)T(n1)T(n) \ge T(n-1)n2n \ge 2)を示します。n=2n = 2 では T(2)=4T(1)=1T(2) = 4 \ge T(1) = 1 です。n3n \ge 3 のとき、n/2(n1)/21\lfloor n/2\rfloor \ge \lfloor (n-1)/2\rfloor \ge 1 であり、帰納法の仮定(nn 未満の引数で TT が単調)から T(n/2)T((n1)/2)T(\lfloor n/2\rfloor) \ge T(\lfloor (n-1)/2\rfloor) です。よって

T(n)=2T(n/2)+n2T((n1)/2)+(n1)=T(n1)T(n) = 2T(\lfloor n/2\rfloor) + n \ge 2T(\lfloor (n-1)/2\rfloor) + (n-1) = T(n-1)

です(n=3n = 3 のときは右辺の漸化式が T(2)T(2) の定義そのものであり、n4n \ge 4 でも同様です)。

次に nn22 のべき乗 2k2^{k} のときの値を求めます。T(2k)=2T(2k1)+2kT(2^{k}) = 2T(2^{k-1}) + 2^{k}T(1)=1T(1) = 1 から、kk についての帰納法で T(2k)=2k(k+1)T(2^{k}) = 2^{k}(k+1) が示せます。実際 k=0k = 0 では 20(0+1)=1=T(1)2^{0}(0+1) = 1 = T(1) で、k1k-1 で成り立てば

T(2k)=22k1k+2k=2kk+2k=2k(k+1)T(2^{k}) = 2\cdot 2^{k-1}k + 2^{k} = 2^{k}k + 2^{k} = 2^{k}(k+1)

です。そこで一般の n1n \ge 1 に対し m=2log2nm = 2^{\lfloor \log_2 n\rfloor} とおくと mnm \le n かつ m>n/2m > n/2 なので、単調性から

T(n)T(m)=m(log2n+1)>n2log2nT(n) \ge T(m) = m\left(\lfloor\log_2 n\rfloor + 1\right) > \frac{n}{2}\log_2 n

です(log2n+1>log2n\lfloor \log_2 n\rfloor + 1 > \log_2 n を使いました)。よって T(n)=Ω(nlogn)T(n) = \Omega(n\log n) であり、上の評価と合わせて T(n)=Θ(nlogn)T(n) = \Theta(n\log n) です。

置換法の要点は、証明したい形をあらかじめ固定してから帰納法に入ることです。O(nlogn)O(n\log n) のようにオーダーだけを仮定して帰納すると、定数が毎回大きくなって発散するという誤りに陥ります。T(n)2nlog2nT(n) \le 2n\log_2 n のように定数まで込みで書き下し、帰納のステップで同じ定数が保たれることを確認してください。

この記事の誤りを報告する ・運営: 夢現技研合同会社料金プラン利用条件特定商取引法に基づく表記

© 2026 夢現技研合同会社 ・本文の LLM への入力は自由です。コード例は MIT ライセンスです。