Big-O notation (“\(f \le g\)”):
Given nondecreasing \(f, g : \N \to \R^+\), we write \(f = O(g)\) if there exists \(c \in \N\) such that \[
f(n) \le c \cdot g(n) \quad \text{for all } n
\] We call \(g\) an asymptotic upper bound for \(f\).
Little-O notation (“\(f < g\)”):
Given nondecreasing \(f, g : \N \to \R^+\), we write \(f = o(g)\) if \[
\lim_{n \to \infty} \frac{f(n)}{g(n)} = 0
\]
Notation | Meaning |
---|---|
\(\quad f(n) = O(g(n))\) | “\(f \le g\)” |
\(\quad f(n) = \Omega(g(n))\) | “\(f \ge g\)” |
\(\quad f(n) = \omega(g(n))\) | “\(f > g\)” |
\(\quad f(n) = \Theta(g(n))\) | “\(f \sim g\)” |
Which of the following statements are correct?
Can every problem be solved in \(O(n)\), \(O(n^2)\), or maybe \(O(n^{100})\) time
if we simply find the right algorithm?
It turns out, no:
for any time bound \(t(n)\), there are problems that can’t be solved in \(O(t(n))\),
but can if we allow more time.
“Given more time, a TM can solve more problems!”
We formalize this by introducing notation for “problems solvable in \(O(t(n))\) time.”
Let \(t: \N \to \N^+\) be a time bound. We define the time complexity class \[ \fragment{\texttt{TIME}(t(n)) =} \fragment{\setbuild{L \subseteq \binary^*}{\fragment{L \text{ is a language decided by a TM with running time } O(t(n))}}} \]
\(\texttt{TIME}(t(n)) \subseteq \powerset(\binary^*)\)
Let \(\texttt{REG}\) denote the class of regular languages.
True or false: \(\texttt{REG} \subseteq \texttt{TIME}(n)\)
Can every problem be solved in \(O(n)\), \(O(n^2)\), or maybe \(O(n^{100})\) time
if we simply find the right algorithm?
It turns out, no:
for any time bound \(t(n)\), there are problems that can’t be solved in \(O(t(n))\),
but can if we allow more time.
“Given more time, a TM can solve more problems!”
We formalize this by introducing notation for “problems solvable in \(O(t(n))\) time.”
Let \(t: \N \to \N^+\) be a time bound. We define the time complexity class \[ \texttt{TIME}(t(n)) = \setbuild{L \subseteq \binary^*}{L \text{ is a language decided by a TM with running time } O(t(n))} \]
\(\texttt{TIME}(t(n)) \subseteq \powerset(\binary^*)\)
For all time bounds \(t_1(n)\) and \(t_2(n)\) such that \(t_1(n) = O(t_2(n))\)
\(\texttt{TIME}(t_1(n)) \subseteq \texttt{TIME}(t_2(n))\)
Let \(t: \N \to \N^+\) be a time bound. We define the time complexity class \[ \texttt{TIME}(t(n)) = \setbuild{L \subseteq \binary^*}{L \text{ is a language decided by a TM with running time } O(t(n))} \]
Time Hierarchy Theorem:
Let \(t_1(n)\) and \(t_2(n)\) be time bounds such that \(t_1(n) \log(n) = o(t_2(n))\).
Then \(\texttt{TIME}(t_1(n)) \subsetneq \texttt{TIME}(t_2(n))\).
There is a problem that can be solved in \(O(t_2(n))\) time, but not \(O(t_1(n))\) time!
Consequences:
A special time complexity class containing problems with “efficient” solutions:
We denote by \(\P\) the class of languages decidable in polynomial time by a (deterministic) Turing machine: \[ \P = \bigcup_{k = 1}^\infty \time{n^k} \]
Remember our model of compute time:
The time complexity of a decider \(M\) is the time bound function \(t : \mathbb{N} \to \mathbb{N}^+\) defined as \[ t(n) = \max_{x \in \binary^n} \texttt{time}_M(x), \]
where \(\texttt{time}_M(x)\) is the number of configurations that \(M\) visits on input \(x\).
Let \(\encoding{\mathcal{X}}_1\) and \(\encoding{\mathcal{X}}_2\) be two different encodings of input object \(\mathcal{X}\) into binary strings.
Define an “encoding blowup factor” from \(\encoding{\mathcal{X}}_1\) to \(\encoding{\mathcal{X}}_2\) as: \[
f_{1 \to 2}(n) = \max_{\mathcal{X} \text{ s.t. } |\encoding{\mathcal{X}}_1| = n} |\encoding{\mathcal{X}}_2|
\]
If \(f_{1 \to 2}(n) = O(n^c)\) and \(f_{2 \to 1}(n) = O(n^c)\), each encoding yields the same definition of \(\P\).
(Assuming encoding/decoding takes polynomial time.)
Example of reasonable encodings for graph \(G = (V, E)\):
Node and edge lists:
\(\encoding{G}_1 = \texttt{ascii\_to\_binary}((1,2,3,4)((1,2),(2,3),(3,1),(1,4)))\)
Binary adjacency matrix: \[ \begin{bmatrix} 0 & 1 & 1 & 1 \\ 1 & 0 & 1 & 0 \\ 1 & 1 & 0 & 0 \\ 1 & 0 & 0 & 0 \\ \end{bmatrix} \quad \Longrightarrow \quad \encoding{G}_2 = 0111101011001000 \hspace{15em} \]
Let \(\encoding{\mathcal{X}}_1\) and \(\encoding{\mathcal{X}}_2\) be two different encodings of input object \(\mathcal{X}\) into binary strings.
Define an “encoding blowup factor” from \(\encoding{\mathcal{X}}_1\) to \(\encoding{\mathcal{X}}_2\) as: \[
f_{1 \to 2}(n) = \max_{\mathcal{X} \text{ s.t. } |\encoding{\mathcal{X}}_1| = n} |\encoding{\mathcal{X}}_2|
\]
If \(f_{1 \to 2}(n) = O(n^c)\) and \(f_{2 \to 1}(n) = O(n^c)\), each encoding yields the same definition of \(\P\).
(Assuming encoding/decoding takes polynomial time.)
Reasonable and unreasonable encodings of \(n \in \N^+\):
\[ \begin{aligned} \encoding{n}_1 \phantom{|} &= \texttt{bin}(n) \in \{0, 1\}^* \\ |\encoding{n}_1| &= \fragment{\lfloor \log_2(n) \rfloor + 1} \fragment{= O(\log n)} \\ \encoding{n}_2 \phantom{|} &= 1^n \\ |\encoding{n}_2| &= n \end{aligned} \hspace{15em} \]
\(f_{1 \to 2}(n) = \fragment{O(2^n)}\)
If we were to care about the different time complexity classes \(O(n^k)\) within \(\P\)
We can try plotting the runtime of our algorithm against different input sizes.
Example: 3sum
If we were to care about the different time complexity classes \(O(n^k)\) within \(\P\)
We can try plotting the runtime of our algorithm against different input sizes.
More specifically, we can use a log-log plot:
Example: 3sum