計算量とは「入力サイズ n n n に対して実行される基本演算の回数」であり、秒で測った実行時間そのものではありません。この抽象化によって、機種・言語・コンパイラに依存しない比較ができます。
f ( n ) = O ( g ( n ) ) f(n) = O(g(n)) f ( n ) = O ( g ( n )) は「ある定数 c > 0 c > 0 c > 0 と n 0 n_0 n 0 が存在して、n ≥ n 0 n \ge n_0 n ≥ n 0 を満たすすべての n n n で f ( n ) ≤ c g ( n ) f(n) \le c\,g(n) f ( n ) ≤ c g ( n ) 」という主張です。O ( g ) O(g) O ( g ) は関数の集合であり、等号は慣用的な略記にすぎません。
増大度には 1 ≺ log n ≺ n ε ≺ n ≺ n log n ≺ n 2 ≺ 2 n ≺ n ! 1 \prec \log n \prec n^{\varepsilon} \prec n \prec n\log n \prec n^2 \prec 2^n \prec n! 1 ≺ log n ≺ n ε ≺ n ≺ n log n ≺ n 2 ≺ 2 n ≺ n ! という階層があります。これは感覚ではなく、極限として証明できる定理です。
入力サイズを 2 倍にすると、Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) のアルゴリズムは 4 倍、Θ ( 2 n ) \Theta(2^n) Θ ( 2 n ) のアルゴリズムは「それまでの全計算量ぶん」時間が増えます。計算機を 1000 倍速くしても、Θ ( 2 n ) \Theta(2^n) Θ ( 2 n ) で扱える n n n は約 10 しか増えません。
分割統治法の漸化式 T ( n ) = a T ( n / b ) + f ( n ) T(n) = a\,T(n/b) + f(n) T ( n ) = a T ( n / b ) + f ( n ) は、マスター定理によって機械的にオーダーが決まります。
「多項式時間で解けるか、指数時間しか知られていないか」という境界が、計算機科学最大の未解決問題である P≠NP 予想の中心にあります。
二つのプログラム A と B のどちらが速いかを知りたいとします。素朴な方法は、実際に走らせて秒数を比べることです。しかしこの方法には決定的な弱点があります。測定したのは「その入力・その計算機・そのコンパイラ・そのときのキャッシュ状態」での速さであって、明日別の環境で同じ順位になる保証がないのです。
もっと悪いことに、小さな入力での測定は大きな入力での挙動をまったく予言しません。要素数 n n n の配列を整列する二つのアルゴリズムを考えます。挿入ソートは最悪の場合およそ n 2 / 2 n^2/2 n 2 /2 回の比較を行い、マージソートはおよそ n log 2 n n\log_2 n n log 2 n 回の比較を行います。n = 10 n = 10 n = 10 なら前者は 45 回、後者は 33 回で、ほとんど差がありません。ところが n = 10 6 n = 10^6 n = 1 0 6 では前者が 5 × 10 11 5 \times 10^{11} 5 × 1 0 11 回、後者が 2 × 10 7 2 \times 10^7 2 × 1 0 7 回となり、比は約 25000 倍に開きます。1 秒間に 10 9 10^9 1 0 9 回の基本演算をこなす計算機なら、後者は 0.02 秒、前者は 8 分以上かかります。
ここで効いているのは実装の巧拙ではなく、n n n が増えたときに演算回数がどう増えるか という構造です。ハードウェアの改良やコードの最適化がもたらすのは、たいていの場合「定数倍の高速化」です。一方アルゴリズムの選択は、n 2 n^2 n 2 を n log n n\log n n log n に変えるという、定数倍では埋まらない差をもたらします。
そこで私たちは、演算回数を n n n の関数として捉え、定数倍と有限個の例外を無視した「増大度」だけを見ることにします。この粗さこそが、環境に依存しない普遍的な比較を可能にします。以下ではまず何を数えるのかを決め(§2)、次に増大度の比較を厳密な言葉にし(§3)、その階層を証明し(§4)、実際のスケールで何が起きるかを見ます(§5)。
「基本演算の回数」を数えるには、何が基本演算なのかを先に決めなければなりません。標準的に用いられるのが一様コスト RAM モデル (uniform-cost random access machine)です。
定義 2.1 (一様コスト RAM モデル )
計算機は、番地 0 , 1 , 2 , … 0, 1, 2, \ldots 0 , 1 , 2 , … で添字づけられた記憶セルの列と、有限個のレジスタを持つとする。次の各操作を基本演算 と呼び、いずれも 1 単位時間で実行されるものとする。
定数、レジスタ、および番地を指定した記憶セルからの読み出しと書き込み
整数・実数の加減乗除と比較
条件分岐と無条件分岐
さらに、1 個の記憶セルには O ( log n ) O(\log n) O ( log n ) ビットの語(n n n は入力サイズ)が格納できるものとする。
最後の条件は見落とされがちですが本質的です。語長を O ( log n ) O(\log n) O ( log n ) ビットに制限しておかないと、1 個のセルに入力全体を詰め込んで多倍長演算を 1 ステップで済ませる、という現実離れしたアルゴリズムが許されてしまいます。逆にこの制限のもとでは、n n n 個の要素に添字を振るのに必要な log 2 n \log_2 n log 2 n ビットはちょうど 1 語に収まり、配列の添字計算が 1 ステップで行えるという、実際の計算機に近い設定になります。
定義 2.3 (最悪時間計算量・空間計算量 )
アルゴリズム A A A と入力 x x x に対し、A A A が x x x 上で停止するまでに実行する基本演算の回数を t A ( x ) t_A(x) t A ( x ) 、書き込みまたは読み出しを行った記憶セルの総数を s A ( x ) s_A(x) s A ( x ) と書く。入力 x x x のサイズ ∣ x ∣ |x| ∣ x ∣ を、x x x を表現するのに要する語数と定める。このとき
T A ( n ) = max ∣ x ∣ = n t A ( x ) , S A ( n ) = max ∣ x ∣ = n s A ( x ) T_A(n) = \max_{|x| = n} t_A(x), \qquad S_A(n) = \max_{|x| = n} s_A(x) T A ( n ) = ∣ x ∣ = n max t A ( x ) , S A ( n ) = ∣ x ∣ = n max s A ( x ) をそれぞれ A A A の最悪時間計算量 、最悪空間計算量 と呼ぶ。
max \max max を取っている点に注意してください。時間計算量は「サイズ n n n の入力のうち最も不利なもの」に対する値です。したがって T A ( n ) T_A(n) T A ( n ) は、n n n さえ決まればどんな入力でも保証される上限になります。
時間と空間は独立ではありません。次の命題は、空間の方が時間より「安い」資源であることを述べています。
命題 2.4 (空間は時間で抑えられる )
アルゴリズム A A A が、1 回の基本演算で高々 κ \kappa κ 個の記憶セルにアクセスするとする(定義 2.1 のモデルではつねに κ ≤ 3 \kappa \le 3 κ ≤ 3 と取れる)。このとき、すべての n n n に対して
S A ( n ) ≤ n + κ T A ( n ) S_A(n) \le n + \kappa\, T_A(n) S A ( n ) ≤ n + κ T A ( n ) が成り立つ。とくに T A ( n ) ≥ n T_A(n) \ge n T A ( n ) ≥ n ならば S A ( n ) = O ( T A ( n ) ) S_A(n) = O(T_A(n)) S A ( n ) = O ( T A ( n )) である。
証明(命題 2.4) サイズ n n n の入力 x x x を固定します。A A A がアクセスするセルは、入力を格納した n n n 個のセルか、実行中にアクセスされたセルのいずれかです。仮定より 1 ステップでアクセスされるセルは高々 κ \kappa κ 個なので、t A ( x ) t_A(x) t A ( x ) ステップ全体でアクセスされるセルは高々 κ t A ( x ) \kappa\,t_A(x) κ t A ( x ) 個です。よって s A ( x ) ≤ n + κ t A ( x ) ≤ n + κ T A ( n ) s_A(x) \le n + \kappa\, t_A(x) \le n + \kappa\,T_A(n) s A ( x ) ≤ n + κ t A ( x ) ≤ n + κ T A ( n ) となり、∣ x ∣ = n |x| = n ∣ x ∣ = n について最大を取れば第 1 の主張を得ます。
T A ( n ) ≥ n T_A(n) \ge n T A ( n ) ≥ n のときは n + κ T A ( n ) ≤ ( 1 + κ ) T A ( n ) n + \kappa T_A(n) \le (1+\kappa) T_A(n) n + κ T A ( n ) ≤ ( 1 + κ ) T A ( n ) なので、定義 3.1 の定数を c = 1 + κ c = 1 + \kappa c = 1 + κ 、n 0 = 1 n_0 = 1 n 0 = 1 と取れば S A ( n ) = O ( T A ( n ) ) S_A(n) = O(T_A(n)) S A ( n ) = O ( T A ( n )) です。
∎
逆は成り立ちません。空間 O ( 1 ) O(1) O ( 1 ) で時間 Θ ( 2 n ) \Theta(2^n) Θ ( 2 n ) のアルゴリズムはいくらでも作れます。「メモリは使い回せるが、時間は使い回せない」という非対称性がここに現れています。
例 2.5 (挿入ソートの比較回数を最後まで数える )
長さ n n n の配列を昇順に並べ替える挿入ソートを考えます。
for i in range ( 1 , len ( a )):
while j >= 0 and a[j] > key:
要素どうしの比較 a[j] > key が何回評価されるかを数えます。外側のループ変数 i i i を固定すると、内側の while は j = i − 1 , i − 2 , … j = i-1, i-2, \ldots j = i − 1 , i − 2 , … と減らしながら回ります。j >= 0 が偽になった時点で短絡評価により比較は行われないので、比較回数は j j j が 0 0 0 以上である間の回数、すなわち高々 i i i 回です。この上限は、入力が狭義単調減少列 a = ( n , n − 1 , … , 1 ) a = (n, n-1, \ldots, 1) a = ( n , n − 1 , … , 1 ) のときちょうど達成されます。実際このとき key はつねに a [ 0.. i − 1 ] a[0..i-1] a [ 0.. i − 1 ] のどの要素よりも小さいので、while は j = − 1 j = -1 j = − 1 になるまで回り、比較は j = i − 1 , … , 0 j = i-1, \ldots, 0 j = i − 1 , … , 0 の i i i 回です。したがって最悪比較回数は
∑ i = 1 n − 1 i = n ( n − 1 ) 2 \sum_{i=1}^{n-1} i = \frac{n(n-1)}{2} i = 1 ∑ n − 1 i = 2 n ( n − 1 ) です。同じ入力に対する代入回数は、i i i ごとに a [ j + 1 ] = a [ j ] a[j+1] = a[j] a [ j + 1 ] = a [ j ] が i i i 回、key の読み書きが 2 回なので ∑ i = 1 n − 1 ( i + 2 ) = n ( n − 1 ) 2 + 2 ( n − 1 ) \sum_{i=1}^{n-1}(i+2) = \frac{n(n-1)}{2} + 2(n-1) ∑ i = 1 n − 1 ( i + 2 ) = 2 n ( n − 1 ) + 2 ( n − 1 ) 回です。基本演算の総数はループ制御を含めても定数倍しか変わらないので、T ( n ) = Θ ( n 2 ) T(n) = \Theta(n^2) T ( n ) = Θ ( n 2 ) となります(Θ \Theta Θ の意味は 定義 3.1 で与えます)。
一方、使う記憶領域は入力の配列に加えて i, j, key の 3 語だけなので、追加空間は Θ ( 1 ) \Theta(1) Θ ( 1 ) です。
以下、f , g f, g f , g は N = { 1 , 2 , … } \mathbb{N} = \{1, 2, \ldots\} N = { 1 , 2 , … } 上で定義された非負実数値関数とします。
定義 3.1 (O 記法・Ω 記法・Θ 記法 )
関数 g g g に対し、関数の集合 O ( g ) O(g) O ( g ) , Ω ( g ) \Omega(g) Ω ( g ) , Θ ( g ) \Theta(g) Θ ( g ) を次で定める。
O ( g ) = { f : ∃ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≤ c g ( n ) } Ω ( g ) = { f : ∃ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≥ c g ( 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} O ( g ) Ω ( g ) Θ ( g ) = { f : ∃ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≤ c g ( n ) } = { f : ∃ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≥ c g ( n ) } = O ( g ) ∩ Ω ( g ) f ∈ O ( g ) f \in O(g) f ∈ O ( g ) を慣用的に f ( n ) = O ( g ( n ) ) f(n) = O(g(n)) f ( n ) = O ( g ( n )) とも書き、「f f f は高々 g g g のオーダーである」と読む。
定義の量化子の順序が要です。c c c と n 0 n_0 n 0 は n n n より先に 選ばれます。つまり「n n n ごとに都合のよい c c c を選ぶ」ことは許されません。この順序を破ると、任意の f , g f, g f , g (g > 0 g > 0 g > 0 )について f ∈ O ( g ) f \in O(g) f ∈ O ( g ) が成り立ってしまい、記法は無意味になります。
flowchart LR
A["定数 c と n0 を先に選ぶ"] --> B["以後どんな n でも"] --> C["n が n0 以上なら f(n) は c·g(n) 以下"] O 記法の定義が主張していること
o o o 記法と ω \omega ω 記法は、量化子を一つ強めたものです。
定義 3.2 (o 記法・ω 記法 )
o ( g ) = { f : ∀ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≤ c g ( n ) } ω ( g ) = { f : ∀ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≥ c g ( 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} o ( g ) ω ( g ) = { f : ∀ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≤ c g ( n ) } = { f : ∀ c > 0 , ∃ n 0 ∈ N , ∀ n ≥ n 0 , f ( n ) ≥ c g ( n ) } f ∈ o ( g ) f \in o(g) f ∈ o ( g ) のとき「f f f は g g g より真に小さいオーダーである」と読む。
O O O では「ある c c c について」だった箇所が o o o では「すべての c c c について」に変わっています。c c c をいくらでも小さく取れるということは、f / g f/g f / g が 0 0 0 に近づくということです。これを正確にしたのが次の命題です。
命題 3.3 (極限による判定法 )
g ( n ) > 0 g(n) > 0 g ( n ) > 0 が十分大きなすべての n n n で成り立つとし、極限 L = lim n → ∞ f ( n ) / g ( n ) L = \lim_{n \to \infty} f(n)/g(n) L = lim n → ∞ f ( n ) / g ( n ) が(+ ∞ +\infty + ∞ を含めて)存在するとする。このとき
0 ≤ L < ∞ 0 \le L < \infty 0 ≤ L < ∞ ならば f ∈ O ( g ) f \in O(g) f ∈ O ( g ) 。
0 < L < ∞ 0 < L < \infty 0 < L < ∞ ならば f ∈ Θ ( g ) f \in \Theta(g) f ∈ Θ ( g ) 。
L = 0 L = 0 L = 0 ならば f ∈ o ( g ) f \in o(g) f ∈ o ( g ) 。
L = ∞ L = \infty L = ∞ ならば f ∈ ω ( g ) f \in \omega(g) f ∈ ω ( g ) 、かつ g ∈ o ( f ) g \in o(f) g ∈ o ( f ) 。
証明(命題 3.3)
L < ∞ L < \infty L < ∞ なので、収束の定義で ε = 1 \varepsilon = 1 ε = 1 と取ると、ある n 1 n_1 n 1 が存在して n ≥ n 1 n \ge n_1 n ≥ n 1 のとき f ( n ) / g ( n ) < L + 1 f(n)/g(n) < L + 1 f ( n ) / g ( n ) < L + 1 です。両辺に g ( n ) > 0 g(n) > 0 g ( n ) > 0 を掛けて f ( n ) ≤ ( L + 1 ) g ( n ) f(n) \le (L+1)\,g(n) f ( n ) ≤ ( L + 1 ) g ( n ) を得ます。定義 3.1 で c = L + 1 > 0 c = L+1 > 0 c = L + 1 > 0 、n 0 = n 1 n_0 = n_1 n 0 = n 1 と取ればよいので f ∈ O ( g ) f \in O(g) f ∈ O ( g ) です。
さらに L > 0 L > 0 L > 0 とします。ε = L / 2 > 0 \varepsilon = L/2 > 0 ε = L /2 > 0 と取ると、ある n 2 n_2 n 2 が存在して n ≥ n 2 n \ge n_2 n ≥ n 2 のとき f ( n ) / g ( n ) > L − L / 2 = L / 2 f(n)/g(n) > L - L/2 = L/2 f ( n ) / g ( n ) > L − L /2 = L /2 、すなわち f ( n ) ≥ ( L / 2 ) g ( n ) f(n) \ge (L/2)\,g(n) f ( n ) ≥ ( L /2 ) g ( n ) です。よって c = L / 2 c = L/2 c = L /2 、n 0 = n 2 n_0 = n_2 n 0 = n 2 で f ∈ Ω ( g ) f \in \Omega(g) f ∈ Ω ( g ) となり、1 と合わせて f ∈ Θ ( g ) f \in \Theta(g) f ∈ Θ ( g ) です。
L = 0 L = 0 L = 0 とします。任意に c > 0 c > 0 c > 0 を与えると、収束の定義で ε = c \varepsilon = c ε = c と取れば、ある n 0 n_0 n 0 が存在して n ≥ n 0 n \ge n_0 n ≥ n 0 のとき f ( n ) / g ( n ) < c f(n)/g(n) < c f ( n ) / g ( n ) < c 、すなわち f ( n ) ≤ c g ( n ) f(n) \le c\,g(n) f ( n ) ≤ c g ( n ) です。c c c は任意だったので 定義 3.2 により f ∈ o ( g ) f \in o(g) f ∈ o ( g ) です。
L = ∞ L = \infty L = ∞ とします。任意の c > 0 c > 0 c > 0 に対し、発散の定義から、ある n 0 n_0 n 0 が存在して n ≥ n 0 n \ge n_0 n ≥ n 0 のとき f ( n ) / g ( n ) > c f(n)/g(n) > c f ( n ) / g ( n ) > c 、すなわち f ( n ) ≥ c g ( n ) f(n) \ge c\,g(n) f ( n ) ≥ c g ( n ) です。よって f ∈ ω ( g ) f \in \omega(g) f ∈ ω ( g ) です。同じ不等式を g ( n ) ≤ ( 1 / c ) f ( n ) g(n) \le (1/c) f(n) g ( n ) ≤ ( 1/ c ) f ( n ) と読み替え、c ′ = 1 / c c' = 1/c c ′ = 1/ c が c c c とともに任意の正数を動くことに注意すれば g ∈ o ( f ) g \in o(f) g ∈ o ( f ) を得ます。
∎
極限が存在しない場合はこの判定法は使えません。それでも O O O 記法自体は意味を持ちます(演習 8.4 を参照)。
命題 3.4 (O 記法の計算則 )
f , f 1 , f 2 , g , g 1 , g 2 , h f, f_1, f_2, g, g_1, g_2, h f , f 1 , f 2 , g , g 1 , g 2 , h を非負実数値関数とする。次が成り立つ。
(反射律)f ∈ O ( f ) f \in O(f) f ∈ O ( f ) 。
(推移律)f ∈ O ( g ) f \in O(g) f ∈ O ( g ) かつ g ∈ O ( h ) g \in O(h) g ∈ O ( h ) ならば f ∈ O ( h ) f \in O(h) f ∈ O ( h ) 。
(定数倍)λ > 0 \lambda > 0 λ > 0 かつ f ∈ O ( g ) f \in O(g) f ∈ O ( g ) ならば λ f ∈ O ( g ) \lambda f \in O(g) λ f ∈ O ( g ) 。
(和)f 1 ∈ O ( g 1 ) f_1 \in O(g_1) f 1 ∈ O ( g 1 ) かつ f 2 ∈ O ( g 2 ) f_2 \in O(g_2) f 2 ∈ O ( g 2 ) ならば f 1 + f 2 ∈ O ( max ( g 1 , g 2 ) ) f_1 + f_2 \in O(\max(g_1, g_2)) f 1 + f 2 ∈ O ( max ( g 1 , g 2 )) 。ここで max ( g 1 , g 2 ) \max(g_1,g_2) max ( g 1 , g 2 ) は各点ごとの最大値を取る関数である。
(積)f 1 ∈ O ( g 1 ) f_1 \in O(g_1) f 1 ∈ O ( g 1 ) かつ f 2 ∈ O ( g 2 ) f_2 \in O(g_2) f 2 ∈ O ( g 2 ) ならば f 1 f 2 ∈ O ( g 1 g 2 ) f_1 f_2 \in O(g_1 g_2) f 1 f 2 ∈ O ( g 1 g 2 ) 。
証明(命題 3.4)
c = 1 c = 1 c = 1 、n 0 = 1 n_0 = 1 n 0 = 1 とすれば、すべての n ≥ 1 n \ge 1 n ≥ 1 で f ( n ) ≤ 1 ⋅ f ( n ) f(n) \le 1 \cdot f(n) f ( n ) ≤ 1 ⋅ f ( n ) です。
仮定より、c 1 > 0 , n 1 c_1 > 0, n_1 c 1 > 0 , n 1 があって n ≥ n 1 n \ge n_1 n ≥ n 1 で f ( n ) ≤ c 1 g ( n ) f(n) \le c_1 g(n) f ( n ) ≤ c 1 g ( n ) 、また c 2 > 0 , n 2 c_2 > 0, n_2 c 2 > 0 , n 2 があって n ≥ n 2 n \ge n_2 n ≥ n 2 で g ( n ) ≤ c 2 h ( n ) g(n) \le c_2 h(n) g ( n ) ≤ c 2 h ( n ) です。n ≥ max ( n 1 , n 2 ) n \ge \max(n_1, n_2) n ≥ max ( n 1 , n 2 ) のとき、第 1 の不等式に第 2 の不等式を代入して f ( n ) ≤ c 1 g ( n ) ≤ c 1 c 2 h ( n ) f(n) \le c_1 g(n) \le c_1 c_2 h(n) f ( n ) ≤ c 1 g ( n ) ≤ c 1 c 2 h ( n ) を得ます(c 1 > 0 c_1 > 0 c 1 > 0 なので不等号の向きは保たれます)。c = c 1 c 2 c = c_1 c_2 c = c 1 c 2 、n 0 = max ( n 1 , n 2 ) n_0 = \max(n_1,n_2) n 0 = max ( n 1 , n 2 ) と取ればよいのです。
n ≥ n 0 n \ge n_0 n ≥ n 0 で f ( n ) ≤ c g ( n ) f(n) \le c\,g(n) f ( n ) ≤ c g ( n ) なら、両辺に λ > 0 \lambda > 0 λ > 0 を掛けて λ f ( n ) ≤ λ c g ( n ) \lambda f(n) \le \lambda c\,g(n) λ f ( n ) ≤ λ c g ( n ) です。定数を λ c \lambda c λ c に取り替えます。
n ≥ max ( n 1 , n 2 ) n \ge \max(n_1,n_2) n ≥ max ( n 1 , n 2 ) のとき、g i ≤ max ( g 1 , g 2 ) g_i \le \max(g_1,g_2) g i ≤ max ( g 1 , g 2 ) を使って
f 1 ( n ) + f 2 ( n ) ≤ c 1 g 1 ( n ) + c 2 g 2 ( n ) ≤ ( c 1 + c 2 ) max ( g 1 ( n ) , g 2 ( 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)) f 1 ( n ) + f 2 ( n ) ≤ c 1 g 1 ( n ) + c 2 g 2 ( n ) ≤ ( c 1 + c 2 ) max ( g 1 ( n ) , g 2 ( n )) です。定数を c 1 + c 2 c_1 + c_2 c 1 + c 2 と取ります。
n ≥ max ( n 1 , n 2 ) n \ge \max(n_1,n_2) n ≥ max ( n 1 , n 2 ) のとき、f 1 , f 2 , g 1 , g 2 ≥ 0 f_1, f_2, g_1, g_2 \ge 0 f 1 , f 2 , g 1 , g 2 ≥ 0 なので不等式どうしを掛けてよく、f 1 ( n ) f 2 ( n ) ≤ c 1 c 2 g 1 ( n ) g 2 ( n ) f_1(n) f_2(n) \le c_1 c_2\, g_1(n) g_2(n) f 1 ( n ) f 2 ( n ) ≤ c 1 c 2 g 1 ( n ) g 2 ( n ) です。
∎
計算則 4 は実務でいちばん使う道具です。「前処理に O ( n log n ) O(n\log n) O ( n log n ) 、本体に O ( n 2 ) O(n^2) O ( n 2 ) かかるアルゴリズムは全体で O ( n 2 ) O(n^2) O ( n 2 ) 」という日常的な推論は、この規則の適用にほかなりません。
命題 3.5 (多項式のオーダー )
d ≥ 0 d \ge 0 d ≥ 0 を整数、a 0 , … , a d a_0, \ldots, a_d a 0 , … , a d を実数、a d > 0 a_d > 0 a d > 0 とし、p ( n ) = ∑ i = 0 d a i n i p(n) = \sum_{i=0}^{d} a_i n^i p ( n ) = ∑ i = 0 d a i n i が十分大きなすべての n n n で非負であるとする。このとき p ∈ Θ ( n d ) p \in \Theta(n^d) p ∈ Θ ( n d ) である。
証明(命題 3.5) まず上からの評価です。A = ∑ i = 0 d ∣ a i ∣ A = \sum_{i=0}^{d} |a_i| A = ∑ i = 0 d ∣ a i ∣ とおくと、n ≥ 1 n \ge 1 n ≥ 1 のとき n i ≤ n d n^i \le n^d n i ≤ n d (0 ≤ i ≤ d 0 \le i \le d 0 ≤ i ≤ d )なので
p ( n ) ≤ ∑ i = 0 d ∣ a i ∣ n i ≤ ( ∑ i = 0 d ∣ a i ∣ ) n d = A n d . 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 . p ( n ) ≤ i = 0 ∑ d ∣ a i ∣ n i ≤ ( i = 0 ∑ d ∣ a i ∣ ) n d = A n d . よって c = A c = A c = A 、n 0 = 1 n_0 = 1 n 0 = 1 として p ∈ O ( n d ) p \in O(n^d) p ∈ O ( n d ) です。
次に下からの評価です。B = ∑ i = 0 d − 1 ∣ a i ∣ B = \sum_{i=0}^{d-1} |a_i| B = ∑ i = 0 d − 1 ∣ a i ∣ とおきます(d = 0 d = 0 d = 0 のときは B = 0 B = 0 B = 0 で、以下は自明に成り立ちます)。n ≥ 1 n \ge 1 n ≥ 1 で
p ( n ) = n d ( a d + ∑ i = 0 d − 1 a i n i − d ) , ∣ ∑ i = 0 d − 1 a i n i − d ∣ ≤ ∑ i = 0 d − 1 ∣ a i ∣ n i − d ≤ B n p(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} p ( n ) = n d ( a d + i = 0 ∑ d − 1 a i n i − d ) , i = 0 ∑ d − 1 a i n i − d ≤ i = 0 ∑ d − 1 ∣ a i ∣ n i − d ≤ n B が成り立ちます。最後の不等号では、i ≤ d − 1 i \le d-1 i ≤ d − 1 より n i − d ≤ n − 1 n^{i-d} \le n^{-1} n i − d ≤ n − 1 を使いました。そこで n 0 = ⌈ 2 B / a d ⌉ + 1 n_0 = \lceil 2B/a_d \rceil + 1 n 0 = ⌈ 2 B / a d ⌉ + 1 と取ると、n ≥ n 0 n \ge n_0 n ≥ n 0 のとき B / n ≤ a d / 2 B/n \le a_d/2 B / n ≤ a d /2 なので
p ( n ) ≥ n d ( a d − a d 2 ) = a d 2 n d p(n) \ge n^d\Big(a_d - \frac{a_d}{2}\Big) = \frac{a_d}{2}\, n^d p ( n ) ≥ n d ( a d − 2 a d ) = 2 a d n d です。c = a d / 2 > 0 c = a_d/2 > 0 c = a d /2 > 0 として p ∈ Ω ( n d ) p \in \Omega(n^d) p ∈ Ω ( n d ) を得ます。両者を合わせて p ∈ Θ ( n d ) p \in \Theta(n^d) p ∈ Θ ( n d ) です。
∎
「log n \log n log n は n n n よりずっと小さい」「指数関数は多項式よりずっと大きい」という感覚は、次の定理として厳密に述べられます。まず基本となる補題を証明します。
補題 4.1 (指数は多項式に勝つ )
c > 1 c > 1 c > 1 と k ≥ 0 k \ge 0 k ≥ 0 を実数とする。このとき実変数の極限として
lim x → ∞ x k c x = 0 \lim_{x \to \infty} \frac{x^k}{c^{\,x}} = 0 x → ∞ lim c x x k = 0 が成り立つ。
証明(補題 4.1) まず x x x が自然数 n n n を動く場合を示します。c > 1 c > 1 c > 1 より h = c − 1 > 0 h = c - 1 > 0 h = c − 1 > 0 と書けます。m = ⌈ k ⌉ + 1 m = \lceil k \rceil + 1 m = ⌈ k ⌉ + 1 とおくと m > k m > k m > k です。二項定理から、n ≥ m n \ge m n ≥ m のとき
c n = ( 1 + h ) n ≥ ( n m ) h m = n ( n − 1 ) ⋯ ( n − m + 1 ) m ! h m ≥ ( n − m + 1 ) m m ! h m c^{\,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 c n = ( 1 + h ) n ≥ ( m n ) h m = m ! n ( n − 1 ) ⋯ ( n − m + 1 ) h m ≥ m ! ( n − m + 1 ) m h m です(各因子 n , n − 1 , … , n − m + 1 n, n-1, \ldots, n-m+1 n , n − 1 , … , n − m + 1 が最小の n − m + 1 n-m+1 n − m + 1 以上であることを使いました)。さらに n ≥ 2 m n \ge 2m n ≥ 2 m なら n − m + 1 > n − m ≥ n / 2 n - m + 1 > n - m \ge n/2 n − m + 1 > n − m ≥ n /2 なので
n k c n ≤ m ! n k h m ( n / 2 ) m = m ! 2 m h m n k − m . \frac{n^k}{c^{\,n}} \le \frac{m!\; n^k}{h^m (n/2)^m} = \frac{m!\,2^m}{h^m}\; n^{\,k-m} . c n n k ≤ h m ( n /2 ) m m ! n k = h m m ! 2 m n k − m . m > k m > k m > k より指数 k − m k - m k − m は負であり、右辺は n → ∞ n \to \infty n → ∞ で 0 0 0 に収束します。n k / c n ≥ 0 n^k/c^n \ge 0 n k / c n ≥ 0 なので、はさみうちにより lim n → ∞ n k / c n = 0 \lim_{n\to\infty} n^k/c^n = 0 lim n → ∞ n k / c n = 0 です。
次に実変数の場合です。x ≥ 1 x \ge 1 x ≥ 1 に対し n = ⌊ x ⌋ + 1 n = \lfloor x \rfloor + 1 n = ⌊ x ⌋ + 1 とおくと x ≤ n x \le n x ≤ n かつ n − 1 ≤ x n - 1 \le x n − 1 ≤ x なので、k ≥ 0 k \ge 0 k ≥ 0 と c > 1 c > 1 c > 1 より
x k c x ≤ n k c n − 1 = c ⋅ n k c n . \frac{x^k}{c^{\,x}} \le \frac{n^k}{c^{\,n-1}} = c\cdot\frac{n^k}{c^{\,n}} . c x x k ≤ c n − 1 n k = c ⋅ c n n k . x → ∞ x \to \infty x → ∞ のとき n → ∞ n \to \infty n → ∞ であり、右辺は前段より 0 0 0 に収束します。したがって lim x → ∞ x k / c x = 0 \lim_{x\to\infty} x^k/c^x = 0 lim x → ∞ x k / c x = 0 です。
∎
定理 4.2 (増大度の階層 )
a > 0 a > 0 a > 0 , ε > 0 \varepsilon > 0 ε > 0 , k ≥ 0 k \ge 0 k ≥ 0 , c > 1 c > 1 c > 1 を任意の実数とする。このとき
( log 2 n ) a ∈ o ( n ε ) , n k ∈ o ( c n ) , c n ∈ o ( n ! ) (\log_2 n)^a \in o(n^{\varepsilon}), \qquad n^k \in o(c^{\,n}), \qquad c^{\,n} \in o(n!) ( log 2 n ) a ∈ o ( n ε ) , n k ∈ o ( c n ) , c n ∈ o ( n !) が成り立つ。とくに ε = 1 \varepsilon = 1 ε = 1 、a = 1 a = 1 a = 1 、c = 2 c = 2 c = 2 と取れば、log 2 n \log_2 n log 2 n 、n n n 、2 n 2^n 2 n 、n ! n! n ! はこの順に真に大きなオーダーである。
証明(定理 4.2) 第 1 の主張。 t = log 2 n t = \log_2 n t = log 2 n とおくと n = 2 t n = 2^t n = 2 t であり、n → ∞ n \to \infty n → ∞ のとき t → ∞ t \to \infty t → ∞ です。このとき
( log 2 n ) a n ε = t a 2 ε t = t a ( 2 ε ) t . \frac{(\log_2 n)^a}{n^{\varepsilon}} = \frac{t^a}{2^{\varepsilon t}} = \frac{t^a}{(2^{\varepsilon})^{t}} . n ε ( log 2 n ) a = 2 εt t a = ( 2 ε ) t t a . ε > 0 \varepsilon > 0 ε > 0 より 2 ε > 1 2^{\varepsilon} > 1 2 ε > 1 なので、補題 4.1 を c = 2 ε c = 2^{\varepsilon} c = 2 ε 、k = a k = a k = a 、x = t x = t x = t として適用すると、この比は 0 0 0 に収束します。命題 3.3 の 3 より ( log 2 n ) a ∈ o ( n ε ) (\log_2 n)^a \in o(n^{\varepsilon}) ( log 2 n ) a ∈ o ( n ε ) です。
第 2 の主張。 補題 4.1 をそのまま x = n x = n x = n に適用すれば n k / c n → 0 n^k/c^n \to 0 n k / c n → 0 であり、再び 命題 3.3 の 3 から n k ∈ o ( c n ) n^k \in o(c^n) n k ∈ o ( c n ) です。
第 3 の主張。 m = ⌈ 2 c ⌉ m = \lceil 2c \rceil m = ⌈ 2 c ⌉ とおきます。n > m n > m n > m のとき
c n n ! = c m m ! ∏ j = m + 1 n c j \frac{c^{\,n}}{n!} = \frac{c^{\,m}}{m!}\prod_{j=m+1}^{n} \frac{c}{j} n ! c n = m ! c m j = m + 1 ∏ n j c と分解できます。j ≥ m + 1 > 2 c j \ge m+1 > 2c j ≥ m + 1 > 2 c より各因子は c / j < 1 / 2 c/j < 1/2 c / j < 1/2 なので、積は ( 1 / 2 ) n − m (1/2)^{\,n-m} ( 1/2 ) n − m 以下です。よって
0 ≤ c n n ! ≤ c m m ! ( 1 2 ) n − m 0 \le \frac{c^{\,n}}{n!} \le \frac{c^{\,m}}{m!}\left(\frac{1}{2}\right)^{n-m} 0 ≤ n ! c n ≤ m ! c m ( 2 1 ) n − m であり、右辺は n → ∞ n \to \infty n → ∞ で 0 0 0 に収束します(c c c と m m m は n n n に依存しない定数です)。はさみうちにより c n / n ! → 0 c^n/n! \to 0 c n / n ! → 0 となり、c n ∈ o ( n ! ) c^n \in o(n!) c n ∈ o ( n !) です。
∎
第 1 の主張は、どんなに小さな ε > 0 \varepsilon > 0 ε > 0 を取っても、( log n ) 100 (\log n)^{100} ( log n ) 100 より n ε n^{\varepsilon} n ε のほうが最終的には大きいと言っています。対数はそれほど遅く増えます。第 2 の主張は、n 1000 n^{1000} n 1000 より 1.001 n 1.001^n 1.00 1 n のほうが最終的には大きいと言っています。指数はそれほど速く増えます。この二つが、次節で見る劇的な差の源です。
1 8 16 24 32 0 25 50 75 100 2ⁿ n² n log n n log n n(入力サイズ) 基本演算の回数 代表的な増大度(縦軸は 100 で打ち切り)
各クラスの性格をつかむ最も手軽な方法は、「入力サイズを 2 倍にしたら実行時間がどうなるか」を見ることです。
命題 5.1 (入力を 2 倍にしたときの比 )
α > 0 \alpha > 0 α > 0 、β \beta β を定数、k > 0 k > 0 k > 0 を実数とする。次が成り立つ。
T ( n ) = α T(n) = \alpha T ( n ) = α ならば T ( 2 n ) / T ( n ) = 1 T(2n)/T(n) = 1 T ( 2 n ) / T ( n ) = 1 。
T ( n ) = α log 2 n + β T(n) = \alpha \log_2 n + \beta T ( n ) = α log 2 n + β ならば T ( 2 n ) − T ( n ) = α T(2n) - T(n) = \alpha T ( 2 n ) − T ( n ) = α (比ではなく差が一定)。
T ( n ) = α n k T(n) = \alpha n^{k} T ( n ) = α n k ならば T ( 2 n ) / T ( n ) = 2 k T(2n)/T(n) = 2^{k} T ( 2 n ) / T ( n ) = 2 k 。
T ( n ) = α n log 2 n T(n) = \alpha n \log_2 n T ( n ) = α n log 2 n ならば T ( 2 n ) / T ( n ) = 2 ( 1 + 1 log 2 n ) T(2n)/T(n) = 2\left(1 + \dfrac{1}{\log_2 n}\right) T ( 2 n ) / T ( n ) = 2 ( 1 + log 2 n 1 ) であり、n → ∞ n \to \infty n → ∞ で 2 2 2 に収束する。
T ( n ) = α 2 n T(n) = \alpha\, 2^{n} T ( n ) = α 2 n ならば T ( 2 n ) / T ( n ) = 2 n T(2n)/T(n) = 2^{n} T ( 2 n ) / T ( n ) = 2 n 。
証明(命題 5.1) いずれも代入して計算します。
T ( 2 n ) / T ( n ) = α / α = 1 T(2n)/T(n) = \alpha/\alpha = 1 T ( 2 n ) / T ( n ) = α / α = 1 。
T ( 2 n ) − T ( n ) = α ( log 2 2 n − log 2 n ) + ( β − β ) = α log 2 2 = α T(2n) - T(n) = \alpha(\log_2 2n - \log_2 n) + (\beta - \beta) = \alpha \log_2 2 = \alpha T ( 2 n ) − T ( n ) = α ( log 2 2 n − log 2 n ) + ( β − β ) = α log 2 2 = α 。
T ( 2 n ) / T ( n ) = α ( 2 n ) k / ( α n k ) = 2 k n k / n k = 2 k T(2n)/T(n) = \alpha (2n)^k / (\alpha n^k) = 2^k n^k / n^k = 2^k T ( 2 n ) / T ( n ) = α ( 2 n ) k / ( α n k ) = 2 k n k / n k = 2 k 。
T ( 2 n ) / T ( n ) = α ⋅ 2 n log 2 2 n α n log 2 n = 2 ⋅ log 2 n + 1 log 2 n = 2 ( 1 + 1 log 2 n ) 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) T ( 2 n ) / T ( n ) = α n log 2 n α ⋅ 2 n log 2 2 n = 2 ⋅ log 2 n log 2 n + 1 = 2 ( 1 + log 2 n 1 ) 。n → ∞ n \to \infty n → ∞ で 1 / log 2 n → 0 1/\log_2 n \to 0 1/ log 2 n → 0 なので比は 2 2 2 に収束します。
T ( 2 n ) / T ( n ) = α 2 2 n / ( α 2 n ) = 2 2 n − n = 2 n T(2n)/T(n) = \alpha 2^{2n}/(\alpha 2^{n}) = 2^{2n-n} = 2^{n} T ( 2 n ) / T ( n ) = α 2 2 n / ( α 2 n ) = 2 2 n − n = 2 n 。
∎
主張 5 が指数時間の恐ろしさを端的に表しています。n = 40 n = 40 n = 40 の問題を解いたあと n = 80 n = 80 n = 80 に進むと、時間は 2 40 ≈ 1.1 × 10 12 2^{40} \approx 1.1\times 10^{12} 2 40 ≈ 1.1 × 1 0 12 倍になります。
例 5.3 (実際の演算回数と実行時間 )
1 秒間に 10 9 10^9 1 0 9 回の基本演算を行う計算機を仮定します。演算回数は次のとおりです。
n n n log 2 n \log_2 n log 2 n n n n n log 2 n n\log_2 n n log 2 n n 2 n^2 n 2 2 n 2^n 2 n 10 10 10 3.3 3.3 3.3 10 10 10 33 33 33 10 2 10^{2} 1 0 2 1.0 × 10 3 1.0\times10^{3} 1.0 × 1 0 3 100 100 100 6.6 6.6 6.6 100 100 100 664 664 664 10 4 10^{4} 1 0 4 1.3 × 10 30 1.3\times10^{30} 1.3 × 1 0 30 10 3 10^{3} 1 0 3 10.0 10.0 10.0 10 3 10^{3} 1 0 3 1.0 × 10 4 1.0\times10^{4} 1.0 × 1 0 4 10 6 10^{6} 1 0 6 天文学的 10 6 10^{6} 1 0 6 19.9 19.9 19.9 10 6 10^{6} 1 0 6 2.0 × 10 7 2.0\times10^{7} 2.0 × 1 0 7 10 12 10^{12} 1 0 12 天文学的 10 9 10^{9} 1 0 9 29.9 29.9 29.9 10 9 10^{9} 1 0 9 3.0 × 10 10 3.0\times10^{10} 3.0 × 1 0 10 10 18 10^{18} 1 0 18 天文学的
これを時間に直します。
T ( n ) T(n) T ( n ) n = 10 6 n = 10^{6} n = 1 0 6 n = 10 9 n = 10^{9} n = 1 0 9 n n n 0.001 0.001 0.001 秒1 1 1 秒n log 2 n n\log_2 n n log 2 n 0.02 0.02 0.02 秒30 30 30 秒n 2 n^2 n 2 17 17 17 分32 32 32 年
n log 2 n n\log_2 n n log 2 n の欄を見てください。n = 10 9 n = 10^9 n = 1 0 9 でも log 2 n \log_2 n log 2 n は 30 30 30 にすぎないので、n log n n\log n n log n は n n n の高々 30 倍です。現実的な入力サイズの範囲では、Θ ( n log n ) \Theta(n\log n) Θ ( n log n ) は Θ ( n ) \Theta(n) Θ ( n ) とほとんど変わりません。 これが、比較ソートや高速フーリエ変換のような Θ ( n log n ) \Theta(n\log n) Θ ( n log n ) アルゴリズムが「実質的に線形」と扱われる理由です。同じことは Θ ( log n ) \Theta(\log n) Θ ( log n ) にも当てはまります。n n n が 10 10 10 から 10 9 10^9 1 0 9 へと 1 億倍になっても、log 2 n \log_2 n log 2 n は 3.3 3.3 3.3 から 29.9 29.9 29.9 へ 9 倍にしかなりません。二分探索が要素数によらず一瞬で終わるように見えるのはこのためです(定理 3.3[探索アルゴリズム] 、探索アルゴリズム を参照)。
指数の側も見ておきます。2 50 ≈ 1.1 × 10 15 2^{50} \approx 1.1\times10^{15} 2 50 ≈ 1.1 × 1 0 15 回は約 13 日、2 100 ≈ 1.3 × 10 30 2^{100} \approx 1.3\times10^{30} 2 100 ≈ 1.3 × 1 0 30 回は約 4 × 10 13 4\times10^{13} 4 × 1 0 13 年で、これは宇宙の年齢(約 1.4 × 10 10 1.4\times10^{10} 1.4 × 1 0 10 年)のおよそ 2900 倍です。階乗はさらに速く、20 ! ≈ 2.4 × 10 18 20! \approx 2.4\times10^{18} 20 ! ≈ 2.4 × 1 0 18 は約 77 年に相当します。
指数時間の本質的な困難は、次の比較で最もはっきりします。
例 5.4 (計算機が 1000 倍速くなったら )
1 秒間に扱える最大の n n n を、10 9 10^9 1 0 9 演算/秒の計算機と 10 12 10^{12} 1 0 12 演算/秒の計算機で比べます。
T ( n ) T(n) T ( n ) 10 9 10^{9} 1 0 9 演算/秒10 12 10^{12} 1 0 12 演算/秒変化 n n n 10 9 10^{9} 1 0 9 10 12 10^{12} 1 0 12 1000 1000 1000 倍n log 2 n n\log_2 n n log 2 n 4.0 × 10 7 4.0\times10^{7} 4.0 × 1 0 7 2.9 × 10 10 2.9\times10^{10} 2.9 × 1 0 10 約 730 730 730 倍 n 2 n^2 n 2 3.2 × 10 4 3.2\times10^{4} 3.2 × 1 0 4 10 6 10^{6} 1 0 6 約 31.6 31.6 31.6 倍 n 3 n^3 n 3 10 3 10^{3} 1 0 3 10 4 10^{4} 1 0 4 10 10 10 倍2 n 2^n 2 n 29 29 29 39 39 39 + 10 +10 + 10
最下行を確かめます。2 n = t 2^{n} = t 2 n = t を解くと n = log 2 t n = \log_2 t n = log 2 t なので、t t t が 1000 1000 1000 倍になったときの n n n の増加は log 2 1000 = 9.97 \log_2 1000 = 9.97 log 2 1000 = 9.97 、つまり約 10 です。これは計算機の速度によらない一般的な事実であり、T ( n ) = α c n T(n) = \alpha\,c^{n} T ( n ) = α c n の形なら増加分は log c 1000 \log_c 1000 log c 1000 です。
n 2 n^2 n 2 の行では 1000 = 31.6 \sqrt{1000} = 31.6 1000 = 31.6 倍、n 3 n^3 n 3 の行では 1000 1 / 3 = 10 1000^{1/3} = 10 100 0 1/3 = 10 倍というように、指数の逆数乗だけ改善します。一般に T ( n ) = α n k T(n) = \alpha n^{k} T ( n ) = α n k なら、計算機が s s s 倍速くなったとき扱える n n n は s 1 / k s^{1/k} s 1/ k 倍になります。
結論は明快です。多項式時間なら計算機の進歩が効きますが、指数時間ではほとんど効きません。 指数時間の壁を破るには、より良いアルゴリズムを見つけるしかないのです。
例 5.5 (総当りから動的計画法へ )
n n n 個の正整数 w 1 , … , w n w_1, \ldots, w_n w 1 , … , w n と目標値 W W W が与えられ、和がちょうど W W W になる部分集合があるかを判定する問題(部分和問題)を考えます。すべての部分集合を列挙する総当りは 2 n 2^n 2 n 通りを調べるので Θ ( 2 n ⋅ n ) \Theta(2^n \cdot n) Θ ( 2 n ⋅ n ) です。n = 40 n = 40 n = 40 なら 2 40 × 40 ≈ 4.4 × 10 13 2^{40} \times 40 \approx 4.4\times10^{13} 2 40 × 40 ≈ 4.4 × 1 0 13 演算、10 9 10^9 1 0 9 演算/秒の計算機で約 12 時間かかります。
一方、b [ i ] [ w ] b[i][w] b [ i ] [ w ] を「最初の i i i 個から和 w w w が作れるか」とする表を埋める動的計画法は Θ ( n W ) \Theta(nW) Θ ( nW ) で済みます。n = 40 n = 40 n = 40 、W = 10 4 W = 10^4 W = 1 0 4 なら 4 × 10 5 4\times10^5 4 × 1 0 5 演算、0.0004 0.0004 0.0004 秒です。3000 万倍以上の高速化ですが、これは計算機を替えたのではなく、同じ部分和を何度も数え直すのをやめただけです(同じ形の漸化式とその計算量は 系 6.4[動的計画法] で扱います。動的計画法 を参照)。
なお Θ ( n W ) \Theta(nW) Θ ( nW ) は入力サイズの多項式ではありません。W W W を表すのに必要なのは log 2 W \log_2 W log 2 W ビットなので、W W W は入力サイズについて指数的に大きくなりえます。このような計算量を擬多項式時間 と呼びます。
同じ操作でも、データ構造を変えれば計算量が変わります。代表的な構造の最悪計算量を並べます(n n n は格納された要素数)。
操作 未整列の配列 整列済み配列 連結リスト 平衡二分探索木 ハッシュ表 値の検索 Θ ( n ) \Theta(n) Θ ( n ) Θ ( log n ) \Theta(\log n) Θ ( log n ) Θ ( n ) \Theta(n) Θ ( n ) Θ ( log n ) \Theta(\log n) Θ ( log n ) 平均 Θ ( 1 ) \Theta(1) Θ ( 1 ) / 最悪 Θ ( n ) \Theta(n) Θ ( n ) 挿入 Θ ( 1 ) \Theta(1) Θ ( 1 ) (末尾)Θ ( n ) \Theta(n) Θ ( n ) Θ ( 1 ) \Theta(1) Θ ( 1 ) (位置既知)Θ ( log n ) \Theta(\log n) Θ ( log n ) 平均 Θ ( 1 ) \Theta(1) Θ ( 1 ) 削除 Θ ( n ) \Theta(n) Θ ( n ) (検索込み)Θ ( n ) \Theta(n) Θ ( n ) Θ ( 1 ) \Theta(1) Θ ( 1 ) (位置既知)Θ ( log n ) \Theta(\log n) Θ ( log n ) 平均 Θ ( 1 ) \Theta(1) Θ ( 1 ) 最小値の取得 Θ ( n ) \Theta(n) Θ ( n ) Θ ( 1 ) \Theta(1) Θ ( 1 ) Θ ( n ) \Theta(n) Θ ( n ) Θ ( log n ) \Theta(\log n) Θ ( log n ) Θ ( n ) \Theta(n) Θ ( n )
万能な構造はありません。整列済み配列は検索が速い代わりに挿入で全体をずらす必要があり、連結リストは挿入が速い代わりに k k k 番目の要素に到達するのに Θ ( k ) \Theta(k) Θ ( k ) かかります(命題 4.2[基本的なデータ構造] )。何を速くしたいかを決めてから構造を選ぶことになります。各構造の定義と、これらの計算量の証明は 基本的なデータ構造 で扱います。
再帰的なアルゴリズムの計算量は漸化式として現れます。マージソートなら、長さ n n n の配列を半分ずつに分けて再帰し、Θ ( n ) \Theta(n) Θ ( n ) 時間で併合するので T ( n ) = 2 T ( n / 2 ) + Θ ( n ) T(n) = 2\,T(n/2) + \Theta(n) T ( n ) = 2 T ( n /2 ) + Θ ( n ) です(定理 4.2[ソートアルゴリズム] )。この形の漸化式は、次の定理で一気に解けます。
定理 6.1 (マスター定理 )
a ≥ 1 a \ge 1 a ≥ 1 、b > 1 b > 1 b > 1 を実数、d > 0 d > 0 d > 0 を定数とし、f f f を b b b のべき乗の上で定義された正の値をとる関数とする。関数 T T T が
T ( 1 ) = d , T ( n ) = a T ( n / b ) + f ( n ) ( n = b k , k ≥ 1 ) T(1) = d, \qquad T(n) = a\,T(n/b) + f(n) \quad (n = b^{k},\ k \ge 1) T ( 1 ) = d , T ( n ) = a T ( n / b ) + f ( n ) ( n = b k , k ≥ 1 ) を満たすとする。n n n は b b b のべき乗の上を動くものとして、次が成り立つ。
ある ε > 0 \varepsilon > 0 ε > 0 について f ( n ) = O ( n log b a − ε ) f(n) = O\!\left(n^{\log_b a - \varepsilon}\right) f ( n ) = O ( n l o g b a − ε ) ならば、T ( n ) = Θ ( n log b a ) T(n) = \Theta\!\left(n^{\log_b a}\right) T ( n ) = Θ ( n l o g b a ) 。
f ( n ) = Θ ( n log b a ) f(n) = \Theta\!\left(n^{\log_b a}\right) f ( n ) = Θ ( n l o g b a ) ならば、T ( n ) = Θ ( n log b a log n ) T(n) = \Theta\!\left(n^{\log_b a}\log n\right) T ( n ) = Θ ( n l o g b a log n ) 。
ある ε > 0 \varepsilon > 0 ε > 0 について f ( n ) = Ω ( n log b a + ε ) f(n) = \Omega\!\left(n^{\log_b a + \varepsilon}\right) f ( n ) = Ω ( n l o g b a + ε ) であり、かつある定数 0 < c < 1 0 < c < 1 0 < c < 1 が存在してすべての k ≥ 1 k \ge 1 k ≥ 1 で a f ( b k − 1 ) ≤ c f ( b k ) a\,f(b^{k-1}) \le c\,f(b^{k}) a f ( b k − 1 ) ≤ c f ( b k ) (正則条件)が成り立つならば、T ( n ) = Θ ( f ( n ) ) T(n) = \Theta(f(n)) T ( n ) = Θ ( f ( n )) 。
証明(定理 6.1) n = b k n = b^{k} n = b k とします。漸化式を k k k 回展開すると
T ( n ) = a k T ( 1 ) + ∑ j = 0 k − 1 a j f ( n b j ) T(n) = a^{k} T(1) + \sum_{j=0}^{k-1} a^{j} f\!\left(\frac{n}{b^{j}}\right) T ( n ) = a k T ( 1 ) + j = 0 ∑ k − 1 a j f ( b j n ) です(k k k についての帰納法:k = 0 k=0 k = 0 では両辺 T ( 1 ) T(1) T ( 1 ) で一致し、k k k で成り立つとして T ( b k + 1 ) = a T ( b k ) + f ( b k + 1 ) T(b^{k+1}) = aT(b^{k}) + f(b^{k+1}) T ( b k + 1 ) = a T ( b k ) + f ( b k + 1 ) に代入すれば k + 1 k+1 k + 1 でも成り立ちます)。ここで
a k = a log b n = ( b log b a ) log b n = ( b log b n ) log b a = n log b a a^{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} a k = a l o g b n = ( b l o g b a ) l o g b n = ( b l o g b n ) l o g b a = n l o g b a なので、第 1 項は d n log b a d\,n^{\log_b a} d n l o g b a です。f > 0 f > 0 f > 0 より、つねに T ( n ) ≥ d n log b a T(n) \ge d\,n^{\log_b a} T ( n ) ≥ d n l o g b a が成り立ちます。あとは和 Σ = ∑ j = 0 k − 1 a j f ( n / b j ) \Sigma = \sum_{j=0}^{k-1} a^{j} f(n/b^{j}) Σ = ∑ j = 0 k − 1 a j f ( n / b j ) を評価します。
場合 1。 仮定より、ある C > 0 C > 0 C > 0 があって f ( m ) ≤ C m log b a − ε f(m) \le C\,m^{\log_b a - \varepsilon} f ( m ) ≤ C m l o g b a − ε (m m m は十分大きい b b b のべき乗)です。すると
Σ ≤ C ∑ j = 0 k − 1 a j ( n b j ) log b a − ε = C n log b a − ε ∑ j = 0 k − 1 a j b − j ( log b a − ε ) . \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)} . Σ ≤ C j = 0 ∑ k − 1 a j ( b j n ) l o g b a − ε = C n l o g b a − ε j = 0 ∑ k − 1 a j b − j ( l o g b a − ε ) . ここで b − j log b a = a − j b^{-j\log_b a} = a^{-j} b − j l o g b a = a − j なので a j b − j ( log b a − ε ) = ( b ε ) j a^{j} b^{-j(\log_b a - \varepsilon)} = (b^{\varepsilon})^{j} a j b − j ( l o g b a − ε ) = ( b ε ) j となり、等比級数の公式から
∑ j = 0 k − 1 ( b ε ) j = b ε k − 1 b ε − 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} j = 0 ∑ k − 1 ( b ε ) j = b ε − 1 b ε k − 1 < b ε − 1 n ε です(b ε k = ( b k ) ε = n ε b^{\varepsilon k} = (b^{k})^{\varepsilon} = n^{\varepsilon} b ε k = ( b k ) ε = n ε を使いました)。よって Σ < C b ε − 1 n log b a \Sigma < \dfrac{C}{b^{\varepsilon}-1}\,n^{\log_b a} Σ < b ε − 1 C n l o g b a であり、T ( n ) = O ( n log b a ) T(n) = O(n^{\log_b a}) T ( n ) = O ( n l o g b a ) です。下からの評価は上で述べた T ( n ) ≥ d n log b a T(n) \ge d\,n^{\log_b a} T ( n ) ≥ d n l o g b a なので、合わせて T ( n ) = Θ ( n log b a ) T(n) = \Theta(n^{\log_b a}) T ( n ) = Θ ( n l o g b a ) を得ます。
場合 2。 仮定より c 1 m log b a ≤ f ( m ) ≤ c 2 m log b a c_1 m^{\log_b a} \le f(m) \le c_2 m^{\log_b a} c 1 m l o g b a ≤ f ( m ) ≤ c 2 m l o g b a となる c 1 , c 2 > 0 c_1, c_2 > 0 c 1 , c 2 > 0 があります。a j ( n / b j ) log b a = a j n log b a a − j = n log b a a^{j}(n/b^{j})^{\log_b a} = a^{j} n^{\log_b a} a^{-j} = n^{\log_b a} a j ( n / b j ) l o g b a = a j n l o g b a a − j = n l o g b a なので、和の各項は c 1 n log b a c_1 n^{\log_b a} c 1 n l o g b a 以上 c 2 n log b a c_2 n^{\log_b a} c 2 n l o g b a 以下です。項数は k = log b n k = \log_b n k = log b n なので
c 1 n log b a log b n ≤ Σ ≤ c 2 n log b a log b n c_1\,n^{\log_b a} \log_b n \le \Sigma \le c_2\,n^{\log_b a}\log_b n c 1 n l o g b a log b n ≤ Σ ≤ c 2 n l o g b a log b n です。log b n \log_b n log b n と log n \log n log n は正の定数倍しか違わない(注意 3.7 )ので、Σ = Θ ( n log b a log n ) \Sigma = \Theta(n^{\log_b a}\log n) Σ = Θ ( n l o g b a log n ) です。第 1 項 d n log b a d\,n^{\log_b a} d n l o g b a はこれに吸収されるので T ( n ) = Θ ( n log b a log n ) T(n) = \Theta(n^{\log_b a}\log n) T ( n ) = Θ ( n l o g b a log n ) です。
場合 3。 正則条件から、j j j についての帰納法で a j f ( n / b j ) ≤ c j f ( n ) a^{j} f(n/b^{j}) \le c^{\,j} f(n) a j f ( n / b j ) ≤ c j f ( n ) が示せます。実際 j = 0 j = 0 j = 0 では等号です。j j j で成り立つとすると、正則条件を n / b j n/b^{j} n / b j に適用して a f ( n / b j + 1 ) ≤ c f ( n / b j ) a f(n/b^{j+1}) \le c f(n/b^{j}) a f ( n / b j + 1 ) ≤ c f ( n / b j ) なので
a j + 1 f ( n / b j + 1 ) = a j ⋅ a f ( n / b j + 1 ) ≤ a j c f ( n / b j ) ≤ c ⋅ c j f ( n ) = c j + 1 f ( 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) a j + 1 f ( n / b j + 1 ) = a j ⋅ a f ( n / b j + 1 ) ≤ a j c f ( n / b j ) ≤ c ⋅ c j f ( n ) = c j + 1 f ( n ) となります。したがって 0 < c < 1 0 < c < 1 0 < c < 1 より
Σ ≤ f ( n ) ∑ j = 0 k − 1 c j < f ( n ) 1 − c . \Sigma \le f(n) \sum_{j=0}^{k-1} c^{\,j} < \frac{f(n)}{1-c} . Σ ≤ f ( n ) j = 0 ∑ k − 1 c j < 1 − c f ( n ) . また仮定 f ( n ) = Ω ( n log b a + ε ) f(n) = \Omega(n^{\log_b a + \varepsilon}) f ( n ) = Ω ( n l o g b a + ε ) から、ある c 3 > 0 c_3 > 0 c 3 > 0 と十分大きな n n n で n log b a ≤ f ( n ) c 3 n ε ≤ f ( n ) c 3 n^{\log_b a} \le \dfrac{f(n)}{c_3\,n^{\varepsilon}} \le \dfrac{f(n)}{c_3} n l o g b a ≤ c 3 n ε f ( n ) ≤ c 3 f ( n ) が成り立つので、第 1 項も O ( f ( n ) ) O(f(n)) O ( f ( n )) です。よって T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T ( n ) = O ( f ( n )) です。一方、展開式で j = 0 j = 0 j = 0 の項を取れば T ( n ) ≥ f ( n ) T(n) \ge f(n) T ( n ) ≥ f ( n ) なので T ( n ) = Ω ( f ( n ) ) T(n) = \Omega(f(n)) T ( n ) = Ω ( f ( n )) であり、T ( n ) = Θ ( f ( n ) ) T(n) = \Theta(f(n)) T ( n ) = Θ ( f ( n )) を得ます。
∎
例 6.3 (マスター定理の適用 )
(a) マージソート。 T ( n ) = 2 T ( n / 2 ) + Θ ( n ) T(n) = 2\,T(n/2) + \Theta(n) T ( n ) = 2 T ( n /2 ) + Θ ( n ) では a = 2 a = 2 a = 2 , b = 2 b = 2 b = 2 , f ( n ) = Θ ( n ) f(n) = \Theta(n) f ( n ) = Θ ( n ) です。log b a = log 2 2 = 1 \log_b a = \log_2 2 = 1 log b a = log 2 2 = 1 なので f ( n ) = Θ ( n 1 ) = Θ ( n log b a ) f(n) = \Theta(n^{1}) = \Theta(n^{\log_b a}) f ( n ) = Θ ( n 1 ) = Θ ( n l o g b a ) となり、場合 2 に当てはまります。よって T ( n ) = Θ ( n log n ) T(n) = \Theta(n\log n) T ( n ) = Θ ( n log n ) です。
(b) 二分探索。 T ( n ) = T ( n / 2 ) + Θ ( 1 ) T(n) = T(n/2) + \Theta(1) T ( n ) = T ( n /2 ) + Θ ( 1 ) では a = 1 a = 1 a = 1 , b = 2 b = 2 b = 2 , f ( n ) = Θ ( 1 ) f(n) = \Theta(1) f ( n ) = Θ ( 1 ) です。log 2 1 = 0 \log_2 1 = 0 log 2 1 = 0 なので n log b a = n 0 = 1 n^{\log_b a} = n^{0} = 1 n l o g b a = n 0 = 1 であり、f ( n ) = Θ ( 1 ) = Θ ( n log b a ) f(n) = \Theta(1) = \Theta(n^{\log_b a}) f ( n ) = Θ ( 1 ) = Θ ( n l o g b a ) です。ふたたび場合 2 に当てはまり、T ( n ) = Θ ( n 0 log n ) = Θ ( log n ) T(n) = \Theta(n^{0}\log n) = \Theta(\log n) T ( n ) = Θ ( n 0 log n ) = Θ ( log n ) です。
(c) Strassen の行列乗算。 T ( n ) = 7 T ( n / 2 ) + Θ ( n 2 ) T(n) = 7\,T(n/2) + \Theta(n^{2}) T ( n ) = 7 T ( n /2 ) + Θ ( n 2 ) では a = 7 a = 7 a = 7 , b = 2 b = 2 b = 2 , f ( n ) = Θ ( n 2 ) f(n) = \Theta(n^{2}) f ( n ) = Θ ( n 2 ) です。log 2 7 = 2.8073 … \log_2 7 = 2.8073\ldots log 2 7 = 2.8073 … なので、ε = 0.5 \varepsilon = 0.5 ε = 0.5 と取れば n log 2 7 − 0.5 = n 2.307 … n^{\log_2 7 - 0.5} = n^{2.307\ldots} n l o g 2 7 − 0.5 = n 2.307 … であり、n 2 = O ( n 2.307 … ) n^{2} = O(n^{2.307\ldots}) n 2 = O ( n 2.307 … ) が成り立ちます。よって場合 1 に当てはまり、T ( n ) = Θ ( n log 2 7 ) T(n) = \Theta(n^{\log_2 7}) T ( n ) = Θ ( n l o g 2 7 ) 、とくに T ( n ) = O ( n 2.808 ) T(n) = O(n^{2.808}) T ( n ) = O ( n 2.808 ) です。素朴な三重ループの Θ ( n 3 ) \Theta(n^{3}) Θ ( n 3 ) より真に小さいオーダーです。
(d) 場合 3 の例。 T ( n ) = 2 T ( n / 2 ) + n 2 T(n) = 2\,T(n/2) + n^{2} T ( n ) = 2 T ( n /2 ) + n 2 では log 2 2 = 1 \log_2 2 = 1 log 2 2 = 1 で、ε = 1 \varepsilon = 1 ε = 1 として n 2 = Ω ( n 1 + 1 ) n^{2} = \Omega(n^{1+1}) n 2 = Ω ( n 1 + 1 ) です。正則条件は 2 ( n / 2 ) 2 = n 2 / 2 ≤ c n 2 2\,(n/2)^{2} = n^{2}/2 \le c\,n^{2} 2 ( n /2 ) 2 = n 2 /2 ≤ c n 2 が c = 1 / 2 < 1 c = 1/2 < 1 c = 1/2 < 1 で成り立つので満たされます。よって T ( n ) = Θ ( n 2 ) T(n) = \Theta(n^{2}) T ( n ) = Θ ( n 2 ) で、再帰の最上段のコストだけで全体が決まります。
ここまで見てきた Θ ( n ) \Theta(n) Θ ( n ) , Θ ( n log n ) \Theta(n\log n) Θ ( n log n ) , Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) , Θ ( n 3 ) \Theta(n^3) Θ ( n 3 ) はいずれも「n n n の多項式で抑えられる」という共通の性質を持ち、Θ ( 2 n ) \Theta(2^n) Θ ( 2 n ) , Θ ( n ! ) \Theta(n!) Θ ( n !) は持ちません。例 5.4 が示したとおり、この境界は計算機の性能向上で動かせません。そこで次の定義が立てられます。
定義 7.1 (多項式時間アルゴリズム )
アルゴリズム A A A が多項式時間 であるとは、ある定数 k k k が存在して T A ( n ) = O ( n k ) T_A(n) = O(n^{k}) T A ( n ) = O ( n k ) が成り立つことをいう。
多項式時間を「効率的」の定義とする立場は Cobham と Edmonds に遡ります。この定義には n 100 n^{100} n 100 も含まれてしまうという難点がありますが、次の 2 点で強い正当性を持ちます。第一に、多項式の集合は加法・乗法・合成について閉じているので、多項式時間アルゴリズムを部品として組み合わせても多項式時間のままです。第二に、この区別は計算モデルの細部に依存しません。RAM モデルとチューリング機械のあいだで計算量は多項式のずれしか生じないため、「多項式時間で解けるか」という問いはモデルを替えても答えが変わらないのです。
多項式時間で解ける判定問題の全体を P \mathrm{P} P 、答えが「はい」のときにその証拠を多項式時間で検証できる判定問題の全体を N P \mathrm{NP} NP (定義 4.1[P≠NP 予想とは何か] )と書きます。P ⊆ N P \mathrm{P} \subseteq \mathrm{NP} P ⊆ NP は定義からすぐ従いますが、逆向きの包含が成り立つかは 1971 年の問題提起以来未解決です。これが P≠NP 予想であり、部分和問題や巡回セールスマン問題を含む数千の問題が「P ≠ N P \mathrm{P} \ne \mathrm{NP} P = NP ならば多項式時間アルゴリズムを持たない」という形で結びついています。詳しくは P≠NP予想とは何か を参照してください。
計算量の記法を学ぶ意味はここにあります。O O O 記法は個々のプログラムの速さを測る道具であると同時に、「何が計算できて何が計算できないか」を論じるための共通言語でもあるのです。
演習 8.1 易
f ( n ) = 3 n 2 + 5 n log 2 n + 100 f(n) = 3n^{2} + 5n\log_2 n + 100 f ( n ) = 3 n 2 + 5 n log 2 n + 100 とする。f ∈ Θ ( n 2 ) f \in \Theta(n^{2}) f ∈ Θ ( n 2 ) であることを、定義 3.1 の定数 c c c と n 0 n_0 n 0 を具体的に与えて示してください。
解答 上からの評価。 n ≥ 2 n \ge 2 n ≥ 2 のとき log 2 n ≤ n \log_2 n \le n log 2 n ≤ n なので 5 n log 2 n ≤ 5 n 2 5n\log_2 n \le 5n^{2} 5 n log 2 n ≤ 5 n 2 です。また n ≥ 10 n \ge 10 n ≥ 10 のとき 100 ≤ n 2 100 \le n^{2} 100 ≤ n 2 です。よって n ≥ 10 n \ge 10 n ≥ 10 のとき
f ( n ) ≤ 3 n 2 + 5 n 2 + n 2 = 9 n 2 f(n) \le 3n^{2} + 5n^{2} + n^{2} = 9n^{2} f ( n ) ≤ 3 n 2 + 5 n 2 + n 2 = 9 n 2 となり、c 2 = 9 c_2 = 9 c 2 = 9 、n 0 = 10 n_0 = 10 n 0 = 10 として f ∈ O ( n 2 ) f \in O(n^{2}) f ∈ O ( n 2 ) です。
下からの評価。 5 n log 2 n ≥ 0 5n\log_2 n \ge 0 5 n log 2 n ≥ 0 (n ≥ 1 n \ge 1 n ≥ 1 )と 100 > 0 100 > 0 100 > 0 より、すべての n ≥ 1 n \ge 1 n ≥ 1 で f ( n ) ≥ 3 n 2 f(n) \ge 3n^{2} f ( n ) ≥ 3 n 2 です。よって c 1 = 3 c_1 = 3 c 1 = 3 、n 0 = 1 n_0 = 1 n 0 = 1 として f ∈ Ω ( n 2 ) f \in \Omega(n^{2}) f ∈ Ω ( n 2 ) です。
両者を合わせ、c 1 = 3 c_1 = 3 c 1 = 3 , c 2 = 9 c_2 = 9 c 2 = 9 , n 0 = 10 n_0 = 10 n 0 = 10 で f ∈ Θ ( n 2 ) f \in \Theta(n^{2}) f ∈ Θ ( n 2 ) です。なお log 2 n ≤ n \log_2 n \le n log 2 n ≤ n (n ≥ 1 n \ge 1 n ≥ 1 )は、定理 4.2 の第 1 主張から log 2 n ∈ o ( n ) \log_2 n \in o(n) log 2 n ∈ o ( n ) が従うことでも保証されますが、ここでは n ≥ 2 n \ge 2 n ≥ 2 で 2 n ≥ n 2^{n} \ge n 2 n ≥ n (n n n についての帰納法:2 2 = 4 ≥ 2 2^{2} = 4 \ge 2 2 2 = 4 ≥ 2 で、2 n ≥ n 2^{n} \ge n 2 n ≥ n なら 2 n + 1 = 2 ⋅ 2 n ≥ 2 n ≥ n + 1 2^{n+1} = 2\cdot 2^{n} \ge 2n \ge n+1 2 n + 1 = 2 ⋅ 2 n ≥ 2 n ≥ n + 1 )から直接得られます。
演習 8.2 標準
log 2 ( n ! ) ∈ Θ ( n log n ) \log_2(n!) \in \Theta(n\log n) log 2 ( n !) ∈ Θ ( n log n ) を示してください。Stirling の公式は使わないこと。
解答 上からの評価。 n ! = ∏ i = 1 n i ≤ ∏ i = 1 n n = n n n! = \prod_{i=1}^{n} i \le \prod_{i=1}^{n} n = n^{n} n ! = ∏ i = 1 n i ≤ ∏ i = 1 n n = n n なので、両辺の log 2 \log_2 log 2 を取って(log 2 \log_2 log 2 は単調増加)
log 2 ( n ! ) ≤ log 2 ( n n ) = n log 2 n . \log_2(n!) \le \log_2(n^{n}) = n\log_2 n . log 2 ( n !) ≤ log 2 ( n n ) = n log 2 n . よって c = 1 c = 1 c = 1 , n 0 = 1 n_0 = 1 n 0 = 1 で log 2 ( n ! ) ∈ O ( n log n ) \log_2(n!) \in O(n\log n) log 2 ( n !) ∈ O ( n log n ) です。
下からの評価。 n ≥ 2 n \ge 2 n ≥ 2 とし、積のうち大きいほうの半分だけを残します。i ≥ ⌈ n / 2 ⌉ i \ge \lceil n/2\rceil i ≥ ⌈ n /2 ⌉ を満たす i i i は少なくとも n / 2 n/2 n /2 個あり、そのそれぞれが n / 2 n/2 n /2 以上なので
n ! ≥ ∏ i = ⌈ n / 2 ⌉ n i ≥ ( n 2 ) n / 2 . n! \ge \prod_{i=\lceil n/2\rceil}^{n} i \ge \left(\frac{n}{2}\right)^{n/2} . n ! ≥ i = ⌈ n /2 ⌉ ∏ n i ≥ ( 2 n ) n /2 . log 2 \log_2 log 2 を取ると
log 2 ( n ! ) ≥ n 2 ( log 2 n − 1 ) . \log_2(n!) \ge \frac{n}{2}\left(\log_2 n - 1\right) . log 2 ( n !) ≥ 2 n ( log 2 n − 1 ) . n ≥ 4 n \ge 4 n ≥ 4 のとき log 2 n ≥ 2 \log_2 n \ge 2 log 2 n ≥ 2 なので log 2 n − 1 ≥ log 2 n − 1 2 log 2 n = 1 2 log 2 n \log_2 n - 1 \ge \log_2 n - \frac{1}{2}\log_2 n = \frac{1}{2}\log_2 n log 2 n − 1 ≥ log 2 n − 2 1 log 2 n = 2 1 log 2 n です。よって
log 2 ( n ! ) ≥ n 4 log 2 n ( n ≥ 4 ) \log_2(n!) \ge \frac{n}{4}\log_2 n \qquad (n \ge 4) log 2 ( n !) ≥ 4 n log 2 n ( n ≥ 4 ) であり、c = 1 / 4 c = 1/4 c = 1/4 , n 0 = 4 n_0 = 4 n 0 = 4 で log 2 ( n ! ) ∈ Ω ( n log n ) \log_2(n!) \in \Omega(n\log n) log 2 ( n !) ∈ Ω ( n log n ) です。合わせて Θ ( n log n ) \Theta(n\log n) Θ ( n log n ) を得ます。
この評価は、比較ソートの最悪比較回数が Ω ( n log n ) \Omega(n\log n) Ω ( n log n ) であるという下界(定理 6.1[ソートアルゴリズム] )の証明に使われます。
演習 8.3 標準
漸化式 T ( 1 ) = 1 T(1) = 1 T ( 1 ) = 1 , T ( n ) = 3 T ( n / 4 ) + n log 2 n T(n) = 3\,T(n/4) + n\log_2 n T ( n ) = 3 T ( n /4 ) + n log 2 n (n n n は 4 4 4 のべき乗)を解いてください。定理 6.1 のどの場合に当てはまるか、正則条件が満たされるかを明示すること。
解答 a = 3 a = 3 a = 3 , b = 4 b = 4 b = 4 , f ( n ) = n log 2 n f(n) = n\log_2 n f ( n ) = n log 2 n です。まず log b a = log 4 3 = 0.7924 … \log_b a = \log_4 3 = 0.7924\ldots log b a = log 4 3 = 0.7924 … を計算します。
どの場合か。 ε = 0.2 \varepsilon = 0.2 ε = 0.2 と取ると log 4 3 + ε = 0.9924 … < 1 \log_4 3 + \varepsilon = 0.9924\ldots < 1 log 4 3 + ε = 0.9924 … < 1 です。n ≥ 2 n \ge 2 n ≥ 2 のとき log 2 n ≥ 1 \log_2 n \ge 1 log 2 n ≥ 1 なので f ( n ) = n log 2 n ≥ n ≥ n 0.9925 f(n) = n\log_2 n \ge n \ge n^{0.9925} f ( n ) = n log 2 n ≥ n ≥ n 0.9925 であり、f ( n ) = Ω ( n log 4 3 + ε ) f(n) = \Omega(n^{\log_4 3 + \varepsilon}) f ( n ) = Ω ( n l o g 4 3 + ε ) が成り立ちます。よって場合 3 の第 1 の条件を満たします。
正則条件。 n = 4 k n = 4^{k} n = 4 k (k ≥ 1 k \ge 1 k ≥ 1 )に対し
a f ( n / b ) = 3 ⋅ n 4 log 2 n 4 = 3 4 n ( log 2 n − 2 ) ≤ 3 4 n log 2 n = 3 4 f ( 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) a f ( n / b ) = 3 ⋅ 4 n log 2 4 n = 4 3 n ( log 2 n − 2 ) ≤ 4 3 n log 2 n = 4 3 f ( n ) です。c = 3 / 4 < 1 c = 3/4 < 1 c = 3/4 < 1 と取れるので正則条件は満たされます(log 2 n − 2 ≤ log 2 n \log_2 n - 2 \le \log_2 n log 2 n − 2 ≤ log 2 n は − 2 ≤ 0 -2 \le 0 − 2 ≤ 0 から従います)。
結論。 定理 6.1 の場合 3 より T ( n ) = Θ ( f ( n ) ) = Θ ( n log n ) T(n) = \Theta(f(n)) = \Theta(n\log n) T ( n ) = Θ ( f ( n )) = Θ ( n log n ) です。再帰の分岐数 3 3 3 が縮小率 4 4 4 に負けているため、最上段のコストだけで全体が決まります。
演習 8.4 難
f ∉ O ( g ) f \notin O(g) f ∈ / O ( g ) かつ g ∉ O ( f ) g \notin O(f) g ∈ / O ( f ) を満たす非負関数の組 f , g f, g f , g を具体的に構成し、証明してください。この例は 命題 3.3 の仮定(極限の存在)が本質的であることを示します。
解答 構成。 N \mathbb{N} N 上の関数を
f ( n ) = { n 2 ( n が偶数 ) n ( n が奇数 ) g ( n ) = { n ( n が偶数 ) n 2 ( 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} f ( n ) = { n 2 n ( n が偶数 ) ( n が奇数 ) g ( n ) = { n n 2 ( n が偶数 ) ( n が奇数 ) と定めます。どちらも非負です。
f ∉ O ( g ) f \notin O(g) f ∈ / O ( g ) の証明。 f ∈ O ( g ) f \in O(g) f ∈ O ( g ) と仮定します。すると、ある c > 0 c > 0 c > 0 と n 0 n_0 n 0 があって、n ≥ n 0 n \ge n_0 n ≥ n 0 のすべての n n n で f ( n ) ≤ c g ( n ) f(n) \le c\,g(n) f ( n ) ≤ c g ( n ) です。ここで n n n として max ( n 0 , ⌈ c ⌉ + 1 ) \max(n_0, \lceil c \rceil + 1) max ( n 0 , ⌈ c ⌉ + 1 ) 以上の偶数 を一つ取ります(そのような偶数は存在します)。この n n n では f ( n ) = n 2 f(n) = n^{2} f ( n ) = n 2 、g ( n ) = n g(n) = n g ( n ) = n なので、不等式は n 2 ≤ c n n^{2} \le c\,n n 2 ≤ c n 、すなわち n ≤ c n \le c n ≤ c となります。ところが n ≥ ⌈ c ⌉ + 1 > c n \ge \lceil c\rceil + 1 > c n ≥ ⌈ c ⌉ + 1 > c なので矛盾です。よって f ∉ O ( g ) f \notin O(g) f ∈ / O ( g ) です。
g ∉ O ( f ) g \notin O(f) g ∈ / O ( f ) の証明。 まったく同じ議論を、偶数のかわりに奇数で行います。g ∈ O ( f ) g \in O(f) g ∈ O ( f ) と仮定して c , n 0 c, n_0 c , n 0 を取り、max ( n 0 , ⌈ c ⌉ + 1 ) \max(n_0, \lceil c\rceil + 1) max ( n 0 , ⌈ c ⌉ + 1 ) 以上の奇数 n n n を取ると g ( n ) = n 2 g(n) = n^{2} g ( n ) = n 2 、f ( n ) = n f(n) = n f ( n ) = n なので n ≤ c n \le c n ≤ c となって矛盾します。
極限との関係。 f ( n ) / g ( n ) f(n)/g(n) f ( n ) / g ( n ) は偶数 n n n で n n n 、奇数 n n n で 1 / n 1/n 1/ n なので、n → ∞ n \to \infty n → ∞ で振動し極限を持ちません。命題 3.3 は極限の存在を仮定していたので、この例には適用できません。O O O 記法による大小関係は全順序ではない、というのがこの演習の教訓です。
T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms , 4th ed., MIT Press, 2022 — 第 3 章(漸近記法)、第 4 章(分割統治と漸化式、マスター定理の床関数つきの形)。
D. E. Knuth, “Big Omicron and big Omega and big Theta”, ACM SIGACT News 8 (1976), 18–24. DOI: 10.1145/1008328.1008329 — O O O , Ω \Omega Ω , Θ \Theta Θ の用法を計算機科学向けに整理した論文。
D. E. Knuth, The Art of Computer Programming, Volume 1: Fundamental Algorithms , 3rd ed., Addison-Wesley, 1997 — 1.2.11 節(漸近的表現)。
R. L. Graham, D. E. Knuth, O. Patashnik, Concrete Mathematics , 2nd ed., Addison-Wesley, 1994 — 第 9 章(Asymptotics)。漸近展開の技法を詳しく扱っています。
J. Kleinberg, É. Tardos, Algorithm Design , Addison-Wesley, 2005 — 第 2 章(アルゴリズム解析の基礎と代表的な計算量クラス)。
M. R. Garey, D. S. Johnson, Computers and Intractability: A Guide to the Theory of NP-Completeness , W. H. Freeman, 1979 — 第 1 章。多項式時間と指数時間の差を計算機の高速化と対比する表があります。
定理 6.1 は n n n が b b b のべき乗である場合に述べました。実際の漸化式は床関数を含み、また分割の形がマスター定理の枠に収まらないこともあります。そのようなときは置換法 、すなわち答えを予想して帰納法で検証する方法が使えます。マージソートの正確な漸化式で見ます。
T ( 1 ) = 1 T(1) = 1 T ( 1 ) = 1 、n ≥ 2 n \ge 2 n ≥ 2 に対し T ( n ) = 2 T ( ⌊ n / 2 ⌋ ) + n T(n) = 2\,T(\lfloor n/2\rfloor) + n T ( n ) = 2 T (⌊ n /2 ⌋) + n と定めます(実際のマージソートは ⌊ n / 2 ⌋ \lfloor n/2 \rfloor ⌊ n /2 ⌋ と ⌈ n / 2 ⌉ \lceil n/2\rceil ⌈ n /2 ⌉ に分けますが、ここでは技法を見るため簡略化した形を扱います)。
上からの評価。 すべての n ≥ 2 n \ge 2 n ≥ 2 で T ( n ) ≤ 2 n log 2 n T(n) \le 2n\log_2 n T ( n ) ≤ 2 n log 2 n が成り立つことを、n n n についての強い帰納法で示します。
n = 2 n = 2 n = 2 :T ( 2 ) = 2 T ( 1 ) + 2 = 4 T(2) = 2T(1) + 2 = 4 T ( 2 ) = 2 T ( 1 ) + 2 = 4 であり、2 ⋅ 2 log 2 2 = 4 2\cdot 2\log_2 2 = 4 2 ⋅ 2 log 2 2 = 4 なので T ( 2 ) ≤ 4 T(2) \le 4 T ( 2 ) ≤ 4 が成り立ちます。
n = 3 n = 3 n = 3 :T ( 3 ) = 2 T ( 1 ) + 3 = 5 T(3) = 2T(1) + 3 = 5 T ( 3 ) = 2 T ( 1 ) + 3 = 5 であり、2 ⋅ 3 log 2 3 = 9.50 … 2\cdot 3\log_2 3 = 9.50\ldots 2 ⋅ 3 log 2 3 = 9.50 … なので成り立ちます。
n ≥ 4 n \ge 4 n ≥ 4 :このとき ⌊ n / 2 ⌋ ≥ 2 \lfloor n/2\rfloor \ge 2 ⌊ n /2 ⌋ ≥ 2 かつ ⌊ n / 2 ⌋ < n \lfloor n/2 \rfloor < n ⌊ n /2 ⌋ < n なので帰納法の仮定が使えて、T ( ⌊ n / 2 ⌋ ) ≤ 2 ⌊ n / 2 ⌋ log 2 ⌊ n / 2 ⌋ T(\lfloor n/2\rfloor) \le 2\lfloor n/2\rfloor \log_2 \lfloor n/2\rfloor T (⌊ n /2 ⌋) ≤ 2 ⌊ n /2 ⌋ log 2 ⌊ n /2 ⌋ です。⌊ n / 2 ⌋ ≤ n / 2 \lfloor n/2\rfloor \le n/2 ⌊ n /2 ⌋ ≤ n /2 と log 2 \log_2 log 2 の単調性から
T ( n ) ≤ 2 ⋅ 2 ⋅ n 2 log 2 n 2 + n = 2 n ( log 2 n − 1 ) + n = 2 n log 2 n − n ≤ 2 n log 2 n T(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 T ( n ) ≤ 2 ⋅ 2 ⋅ 2 n log 2 2 n + n = 2 n ( log 2 n − 1 ) + n = 2 n log 2 n − n ≤ 2 n log 2 n
です。最後の不等号は n > 0 n > 0 n > 0 から従います。
よって T ( n ) = O ( n log n ) T(n) = O(n\log n) T ( n ) = O ( n log n ) です。− n -n − n という余りが残った点が重要で、帰納法が「回った」のはこの余裕があったからです。もし T ( n ) ≤ c n log 2 n T(n) \le c\,n\log_2 n T ( n ) ≤ c n log 2 n を c = 1 c = 1 c = 1 で示そうとすると余りが 0 0 0 以下にならず、帰納が閉じません。
下からの評価。 まず T T T が単調非減少であることを示します。n n n についての強い帰納法で T ( n ) ≥ T ( n − 1 ) T(n) \ge T(n-1) T ( n ) ≥ T ( n − 1 ) (n ≥ 2 n \ge 2 n ≥ 2 )を示します。n = 2 n = 2 n = 2 では T ( 2 ) = 4 ≥ T ( 1 ) = 1 T(2) = 4 \ge T(1) = 1 T ( 2 ) = 4 ≥ T ( 1 ) = 1 です。n ≥ 3 n \ge 3 n ≥ 3 のとき、⌊ n / 2 ⌋ ≥ ⌊ ( n − 1 ) / 2 ⌋ ≥ 1 \lfloor n/2\rfloor \ge \lfloor (n-1)/2\rfloor \ge 1 ⌊ n /2 ⌋ ≥ ⌊( n − 1 ) /2 ⌋ ≥ 1 であり、帰納法の仮定(n n n 未満の引数で T T T が単調)から T ( ⌊ n / 2 ⌋ ) ≥ T ( ⌊ ( n − 1 ) / 2 ⌋ ) T(\lfloor n/2\rfloor) \ge T(\lfloor (n-1)/2\rfloor) T (⌊ n /2 ⌋) ≥ T (⌊( n − 1 ) /2 ⌋) です。よって
T ( n ) = 2 T ( ⌊ n / 2 ⌋ ) + n ≥ 2 T ( ⌊ ( n − 1 ) / 2 ⌋ ) + ( n − 1 ) = T ( n − 1 ) T(n) = 2T(\lfloor n/2\rfloor) + n \ge 2T(\lfloor (n-1)/2\rfloor) + (n-1) = T(n-1) T ( n ) = 2 T (⌊ n /2 ⌋) + n ≥ 2 T (⌊( n − 1 ) /2 ⌋) + ( n − 1 ) = T ( n − 1 )
です(n = 3 n = 3 n = 3 のときは右辺の漸化式が T ( 2 ) T(2) T ( 2 ) の定義そのものであり、n ≥ 4 n \ge 4 n ≥ 4 でも同様です)。
次に n n n が 2 2 2 のべき乗 2 k 2^{k} 2 k のときの値を求めます。T ( 2 k ) = 2 T ( 2 k − 1 ) + 2 k T(2^{k}) = 2T(2^{k-1}) + 2^{k} T ( 2 k ) = 2 T ( 2 k − 1 ) + 2 k と T ( 1 ) = 1 T(1) = 1 T ( 1 ) = 1 から、k k k についての帰納法で T ( 2 k ) = 2 k ( k + 1 ) T(2^{k}) = 2^{k}(k+1) T ( 2 k ) = 2 k ( k + 1 ) が示せます。実際 k = 0 k = 0 k = 0 では 2 0 ( 0 + 1 ) = 1 = T ( 1 ) 2^{0}(0+1) = 1 = T(1) 2 0 ( 0 + 1 ) = 1 = T ( 1 ) で、k − 1 k-1 k − 1 で成り立てば
T ( 2 k ) = 2 ⋅ 2 k − 1 k + 2 k = 2 k k + 2 k = 2 k ( k + 1 ) T(2^{k}) = 2\cdot 2^{k-1}k + 2^{k} = 2^{k}k + 2^{k} = 2^{k}(k+1) T ( 2 k ) = 2 ⋅ 2 k − 1 k + 2 k = 2 k k + 2 k = 2 k ( k + 1 )
です。そこで一般の n ≥ 1 n \ge 1 n ≥ 1 に対し m = 2 ⌊ log 2 n ⌋ m = 2^{\lfloor \log_2 n\rfloor} m = 2 ⌊ l o g 2 n ⌋ とおくと m ≤ n m \le n m ≤ n かつ m > n / 2 m > n/2 m > n /2 なので、単調性から
T ( n ) ≥ T ( m ) = m ( ⌊ log 2 n ⌋ + 1 ) > n 2 log 2 n T(n) \ge T(m) = m\left(\lfloor\log_2 n\rfloor + 1\right) > \frac{n}{2}\log_2 n T ( n ) ≥ T ( m ) = m ( ⌊ log 2 n ⌋ + 1 ) > 2 n log 2 n
です(⌊ log 2 n ⌋ + 1 > log 2 n \lfloor \log_2 n\rfloor + 1 > \log_2 n ⌊ log 2 n ⌋ + 1 > log 2 n を使いました)。よって T ( n ) = Ω ( n log n ) T(n) = \Omega(n\log n) T ( n ) = Ω ( n log n ) であり、上の評価と合わせて T ( n ) = Θ ( n log n ) T(n) = \Theta(n\log n) T ( n ) = Θ ( n log n ) です。
置換法の要点は、証明したい形をあらかじめ固定してから帰納法に入る ことです。O ( n log n ) O(n\log n) O ( n log n ) のようにオーダーだけを仮定して帰納すると、定数が毎回大きくなって発散するという誤りに陥ります。T ( n ) ≤ 2 n log 2 n T(n) \le 2n\log_2 n T ( n ) ≤ 2 n log 2 n のように定数まで込みで書き下し、帰納のステップで同じ定数が保たれることを確認してください。