What is O(log(n!)), O(n!), and Stirling's approximation? - time-complexity

What is O(log(n!)) and O(n!)? I believe it is O(n log(n)) and O(n^n)? Why?
I think it has to do with Stirling's approximation, but I don't get the explanation very well.
Am I wrong about O(log(n!) = O(n log(n))? How can the math be explained in simpler terms? In reality I just want an idea of how this works.

O(n!) isn't equivalent to O(n^n). It is asymptotically less than O(n^n).
O(log(n!)) is equal to O(n log(n)). Here is one way to prove that:
Note that by using the log rule log(mn) = log(m) + log(n) we can see that:
log(n!) = log(n*(n-1)*...2*1) = log(n) + log(n-1) + ... log(2) + log(1)
Proof that O(log(n!)) ⊆ O(n log(n)):
log(n!) = log(n) + log(n-1) + ... log(2) + log(1)
Which is less than:
log(n) + log(n) + log(n) + log(n) + ... + log(n) = n*log(n)
So O(log(n!)) is a subset of O(n log(n))
Proof that O(n log(n)) ⊆ O(log(n!)):
log(n!) = log(n) + log(n-1) + ... log(2) + log(1)
Which is greater than (the left half of that expression with all (n-x) replaced by n/2:
log(n/2) + log(n/2) + ... + log(n/2) = floor(n/2)*log(floor(n/2)) ∈ O(n log(n))
So O(n log(n)) is a subset of O(log(n!)).
Since O(n log(n)) ⊆ O(log(n!)) ⊆ O(n log(n)), they are equivalent big-Oh classes.

By Stirling's approximation,
log(n!) = n log(n) - n + O(log(n))
For large n, the right side is dominated by the term n log(n). That implies that O(log(n!)) = O(n log(n)).
More formally, one definition of "Big O" is that f(x) = O(g(x)) if and only if
lim sup|f(x)/g(x)| < ∞ as x → ∞
Using Stirling's approximation, it's easy to show that log(n!) ∈ O(n log(n)) using this definition.
A similar argument applies to n!. By taking the exponential of both sides of Stirling's approximation, we find that, for large n, n! behaves asymptotically like n^(n+1) / exp(n). Since n / exp(n) → 0 as n → ∞, we can conclude that n! ∈ O(n^n) but O(n!) is not equivalent to O(n^n). There are functions in O(n^n) that are not in O(n!) (such as n^n itself).

Related

Time complexity: O(log n) versus O(log 2n)

Is O(log n) the same as O(log 2n)?
By laws of logarithms, log(2N) = log(2) + log(N) and since you are writing it in big O, what you get is O(log(2)) + O(log(N)) = O(log(N).
Yes, O(log n) and O(log 2n) mean the same thing. This is because
log 2n = log 2 + log n,
and since log 2 is a constant, it's ignored by big-O notation.
Going a bit broader than this, properties of logarithms mean that logs of many common expressions end up being equivalent to O(log n). For example, log nk, for any fixed constant k, is O(log n) because
log nk = k log n = O(log n).

What is the product of O(n) and O(log n)?

Was learning the merge sort algorithm, found that the time complexity of Merge sort is O(n log n).
Want to know if we can say O(n log n) = O(n) * O(log n)?
No, it doesn't really make sense to do that. The Big-O function yields sets of functions and sets cannot be multiplied together.
More generally, you don't normally perform any operations on O(...) results. There's no adding them, subtracting them, multiplying them. No algebra. O(...) typically shows up at the conclusion of a proof: "Based on the analysis above, I conclude that the worst case complexity of Finkle's Algorithm is O(whatever)." It doesn't really show up in the middle where it one might subject it to algebraic manipulation.
(You could perform set operations, I suppose. I've never seen anybody do that.)
To formalise what it means to do O(n) * O(log n), let's make the following definition:
A function f is in O(n) * O(log n) if and only if it can be written as a product f(n) = g(n) h(n) where g is in O(n) and h is in O(log n).
Now we can prove that the set O(n) * O(log n) is equal to the set O(n log n) by showing that the functions in both sets are the same:
Given g in O(n) and h in O(log n), there are N_g, c_g, N_h, c_h such that for all n >= max(N_g, N_h) we have |g(n)| <= c_g n and |h(n)| <= c_h log n. It follows that |g(n) h(n)| <= c_g c_h n log n, and so max(N_g, N_h) and c_g c_h are sufficient to show that f is in O(n log n).
Conversely, given f in O(n log n), there are N_f >= 1, c_f such that |f(n)| <= c_f n log n for all n >= N_f. Define g(n) = max(1, n) and h(n) = f(n) / max(1, n); clearly g is in O(n), and we can also see that for n >= N_f we have |h(n)| <= c_f n log n / max(1, n) where the bound on the right hand side is equal to c_f log n because n >= 1, so N_f, c_f are sufficient to show that h is in O(log n). Since we have f(n) = g(n) h(n), it follows that f is in O(n) * O(log n) as we defined it.
The choice of N_f >= 1 and g(n) = max(1, n) is to avoid dividing by zero when n is zero.
actually, the definition of Big-o is not commutative, lets see the example:
let f be defined as f(n) = n
f(n) = O(n^2) & f(n) = O(n^3), but O(n^2) != O(n^3)
that's because using equal sign = is not accurately define here we should say f(n) is O(g).
anyway being a little inaccurate, here is the definition of Big-O grabbed by sipser:
Say that f (n) = O(g(n))
if positive integers c and n 0 exist such that for every integer n ≥ n0,
f (n) ≤ c g(n).
When f (n) = O(g(n)), we say that g(n) is an upper bound for
When f (n) = O(g(n)), we say that g(n) is an upper bound for
f (n), or more precisely, that g(n) is an asymptotic upper bound for
f (n), to emphasize that we are suppressing constant factors.
So for proving what you state you must first define what * means in your equation. and show for every function which is O(n log n), it is also O(n) * O(log n) and vice-versa.
but being inaccurate again and define * as symbolic polynomial multiplication we have the following for some constant positive c and d.
O(n log n) = O(cn log n) = O(log n ^ (cn)) = O(d log n^(cn)) = O(log (n^cn) ^ d) = O(log n^cdn) ~= log n ^ cdn ~= cdn * log n
= O(n) * O(log n) = O(cn) * O(d log n) = O(cn) * O(log n^d) ~= cn * (log n^d) ~= cn * d*logn ~= cdn * log n

What is the performance of log(n!) compared to n?

I was wondering if log(n!) is O(n), or if n is O(log(n!)). I have seen that log(n!) is O(n log(n)) but there is no information that I can find available to my particular question.
Stirling's approximation implies that log(n!) = Θ(n log(n)). In particular, it is not true that log(n!) = O(n), and it is true that n = O(log(n!)).
Log(n!) grows much faster as compared to n.
You can see from the picture.
Example :
When n =50,
O(n) will be 50.
But, O(log(n!)) = 64.48.
Update :
I tried plotting n and log(n!), on the same Graph.
log(n!) = O(n) for 0 < n < 25
n = O(log(n!)) for n > 25

Quicksort time complextiy analysis (Analysis of recurrence equation)

Quicksort's recurrence equation is
T(n) = T(n/2) + T(n/2) + theta(n)
if pivot always divides the original array into two same-sized sub arrays.
so the overall time complexity would be O(nlogn)
But what if the ratio of the two sub-lists is always 1:99?
The equation definitely would be T(n) = T(n/100) + T(99n/100) + theta(n)
But how can I derive time complexity from the above equation?
I've read other answer which describes that we can ignore T(n/100) since T(99n/100) will dominate the overall time complexity.
But I quite cannot fully understand.
Any advice would be appreciated!
Plug T(n) = n log(n) + Θ(n) and you get
n log(n) + Θ(n) = n/100 log(n/100) + 99n/100 log(99n/100) + Θ(n/100) + Θ(99n/100) + Θ(n)
= n log(n)/100 + 99n log(n)/100 - n/100 log(100) - 99n/100 log(99/100) + Θ(n)
= n log(n) + Θ(n)
In fact any fraction will work:
T(n) = T(pn) + T((1-p)n) + Θ(n)
is solved by O(n log(n)).

When is it okay to replace f(n) terms with g(n)?

So during my lecture my professor demonstrated how to solve this problem...
Prove n^2 + 2n + lgn = O(n^2)
So for this problem if I'm correct, I can replace lgn and 2n with n^2
because it grows faster than those terms. After doing that, we'd end up with n^2 + 2n^2 + n^2 as our g(n)
0 <= n^2 2n + lgn <= n^2 + 2n^2 + n^2 for all n >= 1
However, is this only allowed when proving Big O questions, or can the same methodology be used when trying to prove Big Omega?
So with that being said that leads us to this problem...
Prove 2n^3 - 3n^2 + 2n is Big Omega (n^3)
For this problem he got rid of -3n^2 completely, and only swapped 2n with n^3.
So after doing all of this g(n) would be this.
2n^3 - 3n^2 + 2n >= 2n^3 - n^3 for all n >= 3
Why wasn't -3n^2 replaced with n^3?