メインコンテンツまでスキップ

計算量

ここでは、計算量について学んでいきます。

計算量とは

計算量とは、アルゴリズムの実行に要する計算資源の量です。

計算量には、大きく時間計算量と空間計算量の2つがあります。

時間計算量は、アルゴリズムの実行に要するステップの数です。

空間計算量は、アルゴリズムの実行に要する記憶容量の数です。

計算量オーダー

ここで、計算量を比較することを考えてみましょう。

例えば、次の 2 つのプログラムを考えてみましょう。

Open In Colab
Open In Colab

n=2n = 2 なら、2 番目のプログラムは、1 番目のプログラムの 2 倍の計算が必要です。 しかし、n=10000n = 10000 なら、2 番目のプログラムは、1 番目のプログラムの 10000 倍の計算が必要です。 n=2n = 2 程度なら計算量の差はほとんどありませんが、nn の値が大きくなると非常に大きな差が生まれてきます。

このように、アルゴリズムを比較するときには、入力サイズ nn が大きい時にどうなるのかを考えることが大切になります。 そのため、計算量を比較するときには、入力サイズ nn に対する関数を比較します。 1 番目のプログラムでは、nn 回、2 番目のプログラムでは、n2n^2 回の計算が必要と考えます。

また、nn が十分大きいときには、nnn2n^2 には大きな差が生じますが、2n22n^2n2+nn^2 + nn2n^2 の間にはほとんど違いがありません。このため、定数倍は無視し、最高次数以外の項も考えません。

ランダウの記号

計算量を表すのには、ランダウの記号が便利です。

ランダウの記号は、nn が非常に大きい時の計算量を表します。 例えば、nn 回のステップが必要な時には、O(n)O(n) と表します。

  1. 最高次以外の項は、無視します。例: n2+n+1=O(n2)n^2 + n + 1 = O(n^2)
  2. 係数は無視します。例: 3n2=O(n2)3n^2 = O(n^2)

各オーダーの比較

それぞれのオーダーを比較してみましょう。Python では、1 秒で 10710^7 回ぐらいの計算ができます。10710^7 回を超えたところは、「-」で表します。

データサイズO(1)O(1)O(logn)O(\log n)O(n)O(\sqrt{n})O(n)O(n)O(nlogn)O(n\log n)O(n2)O(n^2)O(n3)O(n^3)O(2n)O(2^n)O(n!)O(n!)
51335122512532120
101441034100100010243628822
20155208740080001048576-
50168502832500125000--
1001710100665100001000000--
100011032100099661000000---
10410^411410010000132878----
10510^51173171000001660965----
10610^612010001000000-----
10710^7124316310000000-----
10810^812710000------
10910^913031623------
101010^{10}134100000------

計算量の求め方

実際に、いくつかの計算量を求めてみましょう。

Open In Colab

このプログラムは for 文で nn 回繰り返されるので、計算量は O(n)O(n) です。

Open In Colab

このプログラムは for 文で n2n^2 回繰り返されるので、計算量は O(n2)O(n^2) です。

Open In Colab

このプログラムは一見すると nn 回の繰り返しに見えますが、g 関数の方にも繰り返しがあり、n2n^2 回の繰り返しになるので、計算量は O(n2)O(n^2) です。

Open In Colab

このプログラムは for 文で n2+n=O(n2)n^2+n=O(n^2) 回繰り返されるので、計算量は O(n2)O(n^2) です。

Open In Colab

再帰関数は少し難しいですがプログラムの流れを追っていくと次のようになっているので、nn 回処理が実行され、計算量は O(n)O(n) です。

Open In Colab

これは、難しいです。図を書くとわかりやすいです。計算量は、この図のノードの数になります。それぞれのノードから、二本の矢印が出ていて、最大 nn 回で fib(0) まで到達するので、計算量は O(2n)O(2^n) よりも小さくなります。

一応、数学的にも、計算しておきます。

計算量を T(n)T(n) とします。

T(n)=T(n1)+T(n2)<2T(n1)(T(n2)<T(n1))<22T(n2)<<2nT(0)=2n\begin{aligned} T(n) &= T(n-1) + T(n-2) \\ &<2T(n-1) \quad (\because T(n-2)<T(n-1)) \\ &<2^2T(n-2) \\ &<\cdots \\ &<2^nT(0) \\ &=2^n \\ \end{aligned}

これで、計算量が O(2n)O(2^n) よりも小さいことが証明できました。

注記

ちなみに、次のようにすると計算量が (2)n\left(\sqrt{2}\right)^n よりも大きいことが証明できます。

T(n)=T(n1)+T(n2)>2T(n2)(T(n1)>T(n2))>22T(n4)>>2n2T(0)=2n2=(2)n\begin{aligned} T(n) &= T(n-1) + T(n-2) \\ &>2T(n-2) \quad (\because T(n-1)>T(n-2)) \\ &>2^2T(n-4) \\ &>\cdots \\ &>2^{\frac{n}{2}}T(0) \\ &=2^{\frac{n}{2}} = \left(\sqrt{2}\right)^n \\ \end{aligned}
注記

計算量は O(2n)O(2^n) よりも小さくなると言いましたが、実際に厳密に計算すると O((1+52)n)O\left(\left(\frac{1+\sqrt{5}}{2}\right)^n\right) になります。(1+521.618\frac{1+\sqrt{5}}{2}\simeq 1.618)

ランダウの記号は、nn が十分大きい時、ある関数以下に抑えられるという意味なので、O(2n)O(2^n) と言っても間違いではないとは思います。

これは、漸化式を解けば求まります。この漸化式は、フィボナッチ数列と同じです。

T(n)=T(n1)+T(n2)T(n)=15{(1+52)n(152)n}=O((1+52)n)\begin{aligned} T(n) &= T(n-1) + T(n-2) \\ &\cdots \\ \therefore T(n) &= \frac{1}{\sqrt{5}}\left\{\left(\frac{1 + \sqrt{5}}{2}\right)^n - \left(\frac{1 - \sqrt{5}}{2}\right)^n\right\} \\ &= O\left(\left(\frac{1+\sqrt{5}}{2}\right)^n\right) \end{aligned}

練習問題 1

次のプログラムの計算量を求めてください。

Open In Colab
解答

for 文の中が n 回繰り返されるので、O(n)O(n) となります。

練習問題 2

次のプログラムの計算量を求めてください。

Open In Colab
解答

こちらも for 文の中がデータ数を nn とすると、 nn 回繰り返されるので、O(n)O(n)となります。

練習問題 3

次のプログラムの計算量を求めてください。

Open In Colab
解答

処理自体は一回しか行われないので、O(1)O(1)となります。