Asymptotic complexity in its simplest form - big-o

I'm studying for my computer science exams and I've came across a few questions on simplifying asymptotic complexity and i'm unsure how far too take it. For example:
Give '2n log(n) + 3 log(n)' in its simplest form.
Which i would consider to be n log n.
Is this correct and is there a method for determining exactly how specific i should be?
Other questions given were:
4 log₄(n + 1)
2n log(n) + 1/2⋅n²
4n² + 3n
And my respective guesses:
log n
n²
3n

Rule of thumb: In Big-O notation, you only have to take the fastest growing part of the sum (if the sum has a constant number of summands) and you can strip off every constant factors.
So your guesses are correct.
In general you should study the mathematical definition of Big-O for more details.
If you are familiar with the math, you can simplify it with all mathematical methods you know.

Related

BigO Notation, understanding

I had seen in one of the videos (https://www.youtube.com/watch?v=A03oI0znAoc&t=470s) that, If suppose f(n)= 2n +3, then BigO is O(n).
Now my question is if I am a developer, and I was given O(n) as upperbound of f(n), then how I will understand, what exact value is the upper bound. Because in 2n +3, we remove 2 (as it is a constant) and 3 (because it is also a constant). So, if my function is f(n) where n = 1, I can't say g(n) is upperbound where n = 1.
1 cannot be upperbound for 1. I find hard understanding this.
I know it is a partial (and probably wrong answer)
From Wikipedia,
Big O notation characterizes functions according to their growth rates: different functions with the same growth rate may be represented using the same O notation.
In your example,
f(n) = 2n+3 has the same growth rate as f(n) = n
If you plot the functions, you will see that both functions have the same linear growth; and as n -> infinity, the difference between the 2 gets minimal.
In Big O notation, f(n) = 2n+3 when n=1 means nothing; you need to look at the trend, not discreet values.
As a developer, you will consider big-O as a first indication for deciding which algorithm to use. If you have an algorithm which is say, O(n^2), you will try to understand whether there is another one which is, say, O(n). If the problem is inherently O(n^2), then the big-O notation will not provide further help and you will need to use other criterion for your decision. However, if the problem is not inherently O(n^2), but O(n), you should discard any algorithm that happen to be O(n^2) and find an O(n) one.
So, the big-O notation will help you to better classify the problem and then try to solve it with an algorithm whose complexity has the same big-O. If you are lucky enough as to find 2 or more algorithms with this complexity, then you will need to ponder them using a different criterion.

Can't understand particalur Big-O example in textbook

So far understanding Big-O notation and how it's calculated is ok...most of the situations are easy to understand. However, I just came across this one problem that I cannot for the life of me figure out.
Directions: select the best big-O notation for the expression.
(n^2 + lg(n))(n-1) / (n + n^2)
The answer is O(n). That's all fine and dandy, but how is that rationalized given the n^3 factor in the numerator? n^3 isn't the best, but I thought there was like a "minimum" basis between f(n) <= O(g(n))?
The book has not explained any mathematical inner-workings, everything has sort of been injected into a possible solution (taking f(n) and generating a g(n) that's slightly greater than f(n)).
Kinda stumped. Go crazy on the math, or math referencing, if you must.
Also, given a piece of code, how does one determine the time units per line? How do you determine logarithmic times based off of a line of code (or multiple lines of code)? I understand that declaring and setting a variable is considered 1 unit of time, but when things get nasty, how would I approach a solution?
If you throw this algorithm into Wolfram Alpha, you get this generic result:
If you expand (FOIL) it, you get (roughly) a cubic function divided by a quadratic function. With Big-O, constants don't matter and the larger power wins, so you'd result with something like this:
The rest from here is mathematical induction. The overall algorithm grows in a linear-like fashion with respect to larger and larger values of n. It's not quite linear so we can't say it has a Big-Omega of (n), but it does come fairly reasonably close to O(n) due to the amortized constant growth rate.
Alternatively, you could annoy mathematicians everywhere and say, "Since this is based on Big-O rules, we can drop the factor of n from the denominator and thus result in O(n) by simple division." However, it's important in my mind to consider that this is still not quite linear.
Mind, this is a less-rigorous explanation than might be satisfactory for your class, but this gives you some math-based perspective on its runtime.
Non-rigorous answer:
Distributing the numerator product, we find that the numerator is n^3 + n log(n) - n^2 - log n.
We note that the numerator grows as n^3 for large n, and the denominator grows as n^2 for large n.
We interpret that as growth as n^{3 - 2} for large n, or O(n).

Why can we assume that for T(n) = 2T(n/2) + theta(1), n is a power of 2?

Lately I have been interested in algorithms and I have been watching a video series published by MIT. I encountered some problems regarding recurrence though.
https://www.youtube.com/watch?v=-EQTVuAhSFY
The attached link is the source of the video. At 07:10, the professor mentioned that it's fine to use T(n/2) in T(n) = 2T(n/2) + theta(1) due to a certain theorem, despite that it would be more accurate to use T(the floor of n/2) or T(the ceiling of n/2).
What exactly is this theorem? Actually I'm kind of confused because n/2 might not generate the base case input for some n.
e.g. some initial input that is not a power of 2.
Great question. I'm sure you learnt about the Big-O in that lesson, so I shall not elaborate on that. (My explanation will use O(N) which for the ease of explanation, can be assumed as the same as theta(N), but they are different!)
The important part of Big-O (and theta) is SIGNIFICANCE. For example, O(N) will always be more significant than O(1), even if it was 99999*O(1) vs O(N).
So, what your professor is trying to say is that when you do n/2, you do NOT have to floor or ceiling it because the extra bit that you do away with is not significant. What you are dealing with is runtime in a Big-O scenario, which does not care about the nitty bitty details. We are assuming N is HUGE, and your little extra bit of time spent trying to floor or ceiling N is never comparable.
Basically for Big-O, you want sweeping generalizations, which thankfully means you can assume n is a power of 2!

T(n) converting to O(n)

I am a little confused on the difference between T(N) and O(N) when dealing with time complexity. I have three algorithms with their respective T(N) equations and I have to find the worst case time complexity O(N) and I’m not sure how that differs from T(N).
An example would be:
T(n) = 150⋅N² + 3⋅N + 11⋅log₂(N)
Would the O() be just O(N²)
Also, should the algorithm with the lower order of complexity always be used? I have a feeling the answer is no but I'm not too sure as to why.
Would the O() be just O(N²)
Yes.
For large N, the N² term will dominate the runtime, so that the other terms don't matter anymore.
E.g., for N=10, in your example, 150⋅N² is already 15000, while 3⋅N = 30 and 11⋅log₂(N) = 36.5, so the non-N² terms make up only 0.44% of the total number of steps.
For N=100, 150⋅N² = 1500000, 3⋅N = 300, 11⋅log₂(N) = 73.1, so the non-N² terms make up only 0.025% of the total number of steps.
So for higher N, the relevance of lower order terms diminishes.
Also, should the algorithm with the lower order of complexity always be used?
No. Because Big-O notation describes only the asymptotic behavior as N gets large, and does not include any constant factor overhead, you may often be better off using a less optimal algorithm with a lower overhead.
In your example, if I have an alternate algorithm for the problem you are trying to solve that has runtime T'(N) = 10⋅N³, then for N=10 it will take only 10000 steps, while your example would require 150067 steps. Basically, for any N ≤ 15, the T'(N) algorithm would be faster, and for any N > 15, your T(N) algorithm would be faster. So if you know in advance that you are not going to see any N > 15, you are better off by choosing the theoretically less efficient algorithm T'(N).
Of course, in practice there are many other considerations as well, such as:
Availability of algorithms that you can reuse in libraries, on the web, etc.
If you implement it yourself: ease of implementation
Whether or not the algorithm scales to multiple cores or multiple machines easily
T(n) is the function representing the time taken for an input of size n. Big-oh notation is a classification of that. Like you said in your example, the big-oh of that example would be n^2.
Regarding your second question, big-oh notation indicates the algorithm you should use as the input size approaches infinity. Practically speaking, there are cases where you would never get an input large enough to compensate.
For example, if T1(n) = 999999999999*N and T2(n) = 2*N^2, eventually n is large enough for T2 to be greater than T1. However, for smaller sizes of n, T1 is greater. You can graph the functions, or even solve a system of equations to find out what size of n will make a difference.
Note: Also keep in mind that big-oh is a bound on the complexity, which means that you can have a loose bound which is still correct.
T(n) is just a function. O or big oh is a level of complexity.
T(n) can be f(n) or g(n) for that matter.
I hope that is clear.
Big Oh is a measure of the time or space complexity of an algorithm.
You dont consider the lower order for complexity because for very large values of n, the higher order complexity is >> lower order complexity.

Asymptotic Notations and forming Recurrence relations by analysing the algorithms

I went through many lectures, videos and sources regarding Asymptotic notations. I understood what O, Omega and Theta were. But in algorithms, why do we use only Big Oh notation always, why not Theta and Omega (I know it sounds noobish, but please help me with this). What exactly is this upperbound and lowerbound in accordance with Algorithms?
My next question is, how do we find the complexity from an algorithm. Say I have an algorithm, how do I find the recurrence relation T(N) and then compute the complexity out of it? How do I form these equations? Like in the case of Linear Search using Recursive way, T(n)=T(N-1) + 1. How?
It would be great if someone can explain me considering me a noob so that I can understand even better. I found some answers but wasn't convincing enough in StackOverFlow.
Thank you.
Why we use big-O so much compared to Theta and Omega: This is partly cultural, rather than technical. It is extremely common for people to say big-O when Theta would really be more appropriate. Omega doesn't get used much in practice both because we frequently are more concerned about upper bounds than lower bounds, and also because non-trivial lower bounds are often much more difficult to prove. (Trivial lower bounds are usually the kind that say "You have to look at all of the input, so the running time is at least equal to the size of the input.")
Of course, these comments about lower bounds also partly explain Theta, since Theta involves both an upper bound and a lower bound.
Coming up with a recurrence relation: There's no simple recipe that addresses all cases. Here's a description for relatively simple recursive algorithmms.
Let N be the size of the initial input. Suppose there are R recursive calls in your recursive function. (Example: for mergesort, R would be 2.) Further suppose that all the recursive calls reduce the size of the initial input by the same amount, from N to M. (Example: for mergesort, M would be N/2.) And, finally, suppose that the recursive function does W work outside of the recursive calls. (Example: for mergesort, W would be N for the merge.)
Then the recurrence relation would be T(N) = R*T(M) + W. (Example: for mergesort, this would be T(N) = 2*T(N/2) + N.)
When we create an algorithm, it's always in order to be the fastest and we need to consider every case. This is why we use O, because we want to major the complexity and be sure that our algorithm will never overtake this.
To assess the complexity, you have to count the number of step. In the equation T(n) = T(n-1) + 1, there is gonna be N step before compute T(0), then the complixity is linear. (I'm talking about time complexity and not space complexity).

Resources