Complexity
Time Complexity
Space Complexity
We generally mean auxiliary space complexity which excludes space for the input
Asymptotic Notation
Syntax
All functions defined are for n ∈ N 0 or its subset and the functions are asymptotically non-negative , that is the functions are non-negative for sufficiently large n
When we say f ( n ) = O ( g ( n )) we actually mean f ( n ) ∈ O ( g ( n ))
More precisely
O ( g ( n )) = { f ( n ) : ∃ c > 0 , n 0 > 0 such that 0 ≤ f ( n ) ≤ c ⋅ g ( n ) , ∀ n ≥ n 0 }
Big Notations
Big-O
Upper bound
f ( n ) ∈ O ( g ( n )) ⟺ ∃ c > 0 , n 0 > 0 : f ( n ) ≤ c ⋅ g ( n ) ∀ n ≥ n 0
Big-Theta
Tight Bound
f ( n ) ∈ Θ ( g ( n )) ⟺ ∃ c 1 , c 2 > 0 , n 0 > 0 : c 1 ⋅ g ( n ) ≤ f ( n ) ≤ c 2 ⋅ g ( n ) ∀ n ≥ n 0
Big-Omega
Lower Bound
f ( n ) ∈ Ω ( g ( n )) ⟺ ∃ c > 0 , n 0 > 0 : c ⋅ g ( n ) ≤ f ( n ) ∀ n ≥ n 0
O , Ω , Θ themselves aren’t inherently “worst-case”
Relationship between Big-Theta, Big-O and Big-Omega
f ( n ) ∈ Θ ( g ( n )) ⟺ f ( n ) ∈ O ( g ( n )) and f ( n ) ∈ Ω ( g ( n ))
f ( n ) ∈ Θ ( g ( n )) ⟺ g ( n ) ∈ Θ ( f ( n ))
little Notations
little-o
Strict upper bound
f ( n ) ∈ o ( g ( n )) ⟺ ∀ c > 0 , ∃ n 0 > 0 : f ( n ) < c ⋅ g ( n ) ∀ n ≥ n 0
little-omega
Strict lower bound
f ( n ) ∈ ω ( g ( n )) ⟺ ∀ c > 0 , ∃ n 0 > 0 : c ⋅ g ( n ) < f ( n ) ∀ n ≥ n 0
Relationships between Big and little notations
o ( g ) ⊆ O ( g ) ω ( g ) ⊆ Ω ( g ) f ( n ) ∈ o ( g ( n )) ⟹ f ( n ) ∈ O ( g ( n )) f ( n ) ∈ ω ( g ( n )) ⟹ f ( n ) ∈ Ω ( g ( n )) o ( f ( n )) ∩ ω ( f ( n )) = ϕ
Example with Asymptotic Notation
Assume worst case time complexity of an algorithm
Big-Omega
T ( n ) ∈ Ω ( 1 ) , Ω ( n ) , Ω ( n log n ) , Ω ( n 2 )
there are infinitely many lower bounds
small-omega
T ( n ) ∈ ω ( 1 ) , ω ( n ) , ω ( n log n )
there are infinitely many strict lower bounds
Big-Theta
Big-O
T ( n ) ∈ O ( n 2 ) , O ( n 3 ) , O ( 2 n )
there are infinitely many upper bounds
small-o
T ( n ) ∈ o ( n 3 ) , o ( 2 n )
there are infinitely many strict upper bounds
Idea
o ( g ) = asymptotically strictly slower functions than g
O ( g ) = asymptotically slower + same rate functions as g
Θ ( g ) = asymptotically same rate functions as g
Ω ( g ) = asymptotically faster + same rate functions as g
ω ( g ) = asymptotically faster functions than g
Assume
A < B : The functions represented by B grow strictly faster than the functions represented by A
A ≤ B : The functions represented by B grow faster or same rate as functions represented by A
Then
o ( g ) < Θ ( g ) < ω ( g ) O ( g ) ≤ Θ ( g ) ≤ Ω ( g )
Comparison of common complexities
O ( 1 ) < O ( log n ) < O ( n ) < O ( n log n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n )
Complexity of Algorithms
Running Time is denoted by T ( n )
Cases
Worst case
Average case
Best case
Amortized case
Default convention
When asked for an algorithm’s complexity, we usually analyze its worst-case complexity
We commonly express it using Big-O notation, often intending the tightest asymptotic upper bound
Why Big-O instead of Big-Theta?
Big-O is widely used as shorthand for algorithm complexity
Sometimes we cannot establish tight bound easily, hence Big-Theta cannot be used
When a tight bound is known, Big-Theta is perfectly valid and more informative
Average case complexity
We can compute using Probability distribution
T avg ( n ) = i = 1 ∑ m P ( x i ) T ( x i )
T avg ( n ) = m 1 i = 1 ∑ m T ( x i )
Amortized case complexity
Very similar to equally likely average case
Example:
O ( 1 ) Time complexity for appending new element in dynamic array which internally performs resizing
Is it defined for sequence of operations???
How is it different from average case??