How to calculate worst case analysis of this algorithm? - algorithm

sum = 0;
for(int i = 0; i < N; i++)
for(int j = i; j >= 0; j--)
sum++;
From what I understand, the first line is 1 operation, 2nd line is (i+1) operations, 3rd line is (i-1) operations, and 4th line is n operations. Does this mean that the running time would be 1 + (i+1)(i-1) + n? It's just these last steps that confuse me.

To analyze the algorithm you don't want to go line by line asking "how much time does this particular line contribute?" The reason is that each line doesn't execute the same number of times. For example, the innermost line is executed a whole bunch of times, compared to the first line which is run just once.
To analyze an algorithm like this, try identifying some quantity whose value is within a constant factor of the total runtime of the algorithm. In this case, that quantity would probably be "how many times does the line sum++ execute?", since if we know this value, we know the total amount of time that's spent by the two loops in the algorithm. To figure this out, let's trace out what happens with these loops. On the first iteration of the outer loop, i == 0 and so the inner loop will execute exactly once (counting down from 0 to 0). On the second iteration of the outer loop, i == 1 and the inner loop executes exactly twice (first with j == 1, once with j == 0. More generally, on the kth iteration of the outer loop, the inner loop executes k + 1 times. This means that the total number of iterations of the innermost loop is given by
1 + 2 + 3 + ... + N
This quantity can be shown to be equal to
N (N + 1) N^2 + N N^2 N
--------- = ------- = --- + ---
2 2 2 2
Of these two terms, the N^2 / 2 term is the dominant growth term, and so if we ignore its constant factors we get a runtime of O(N2).
Don't look at this answer as something you should memorize - think of all of the steps required to get to the answer. We started by finding some quantity to count, and then saw how that quantity was influenced by the execution of the loops. From this, we were able to derive a mathematical expression for that quantity, which we then simplified. Finally, we took the resulting expression and determined the dominant term, which serves as the big-O for the overall function.

Work from inside-out.
sum++
This is a single operation on it's own, as it doesn't repeat.
for(int j = i; j >= 0; j--)
This loops i+1 times. There are several operations in there, but you probably don't mean to count the number of asm instructions. So I'll assume for this question this is a multiplier of i+1. Since the loop contents is a single operation, the loop and its block perform i+1 operations.
for(int i = 0; i < N; i++)
This loops N times. So as before, this is a multiplier of N. Since the block performs i+1 operations, this loop performs N(N+1)/2 operations in total. And that's your answer! If you want to consider big-O complexity, then this simplifies to O(N2).

It's not additive: the inner loop happens once for EACH iteration of the outer loop. So it's O(n2).
By the way, this is a good example of why we use asymptotic notation for this kind of thing -- depending on the definition of "operation" the exact details of the count could vary pretty widely. (Like, is sum++ a single operation, or is it add sum to 1 giving temp; load temp to sum?) But since we know that all that can be hidden in a constant factor, it's still going to be O(n2).

No; you don't count a specific number of operations for each line and then add them up. The entire point of constructions like 'for' is to make it possible for a given line of code to run more than once. You're supposed to use thinking and logic skills to figure out how many times the line 'sum++' will run, as a function of N. Hint: it runs once for every time that the third line is encountered.
How many times is the second line encountered?
Each time the second line is encountered, the value of 'i' is set. How many times does the third line run with that value of i? Therefore, how many times will it run overall? (Hint: if I give you a different amount of money on several different occasions, how do you find out the total amount I gave you?)
Each time the third line is encountered, the fourth line happens once.
Which line happens most often? How often does it happen, in terms of N?

So guess what interest you is the sum++ and how many time you execute it.
The final stat of sum would give you that answer.
Actually your loop is just:
Sigma(n) n goes from 1 to N.
Which equal to: N*(N+1) / 2 This give you in big-o-notation O(N^2)
Also beside the name of you question there is no worst case in you algorithm.
Or you could say that the worst case is when N goes to infinity.

Using Sigma notation to represent your loops:

Related

How is this nested loop algorithm analyzed?

So i have this algorithm analysis from my lecturer i need some help why the outer loop is n - 1 , isn't it should be n - 2? and the inner loop should be log3 (n) instead of log3(n) + 1
for(int a=3; a<=n; a++) n+1-2 = n-1
for(int a=1; a<n; a=a*3) log3 (n) +1
System.out.println(a); log3 (n)
Total =(n - 1)* (log3 (n)+1+log3 (n))
=(n-1)*(2 log3(n) + 1)
=2(n log3(n))+n -1 – 2 log3(n)
=n log3(n) + n – log3(n)
Is this correct answer for algorithmn analysis? thats what my lecturer showed me. Anyone can explain to me?
If you want a very precise count of the operations, it's important to specify: what operations are you counting? Number of increments a++? Number of comparisons a<=n? Number of executions of the loop body?
If you don't specify which operation you're counting, then there is not much sense in worrying about an extra +1 or -1.
Note that the variable used as counter for the outer loop is called a and the variable used as counter for the inner loop is also called a. While this is possible to do in C++, I strongly recommend not doing it. It's confusing and a good source of errors.
The outer loop is going to run for n-2 iterations. The inner loop is going to run for ceil(log3(n)) iterations (where ceil is the ceiling function). The line System.out.println(a) is not a loop, it's just one line of code, so you could write "1" on that line if you wanted; there is not much sense in writing "log3(n)" here.
The total number of times the line System.out.println(a) is executed is thus:
(n - 2) ceil(log3(n)).
It is possible that you might want to count the exact number of characters written. Again, we're coming back to the fact that you didn't specify what it is that you were counting. The number of characters depends on the exact value of a, so it changes at each iteration of the loop. But all in all, each call to System.out.println(a) prints about log10(n) characters, since we're writing in decimal.

For loop - big-O

I am trying to do this problem out of a book and am struggling to understand the answer.
for (i = 0; i < N; ++i) {
if ((i % 2) == 0) {
outVal[i] = inVals[i] * i
}
}
here's how I was breaking it down:
I=0 -> executes 1 time
I < n and ++I each execute once every iteration. so 1n+1n = 2n.
the if statement contains 2 operands, so now we are at 4n+1.
the contents of the if statement only executes n/2 times, so we are at 4n+1+n/2
however, big O drops those terms off, leaving us with N as the answer
Here's what I don't get: the explanation for the answer of my problem says this:
outVal[i] = inVals[i] * i; executes every other loop iteration, so the total number of operations include: 1 operation at the start of the loop, 3 operations every loop iteration, 1 operation every other loop iteration, and 1 operation for the final loop condition check.
how are there only 3 operations in the loop? I counted 4 as stated above. Please let me know the rationale behind this.
The complexity is measured by the time/space you take to accomplish a task. i<N and ++i do not take time dependant of your space variable N (the length of the loop).
You must not add how many times an operation is done and sum all of them - you must, instead, choose the one who takes more time or space, as that's the algorithm bottleneck. In a loop, msot of the operations run equal times, so we use the length of the loop as its complexity space or time.
The loop will run N times, so that's its complexity -> O(n)
Inside the loop, the if scope will run N/2 times, as you correctly said -> O(n/2)
But those runs are already added to the first loop iterations. You will not add it since there are no external iterations.
So, the complexity of the algorithm is O(n).
Regarding the operations, the 3 are:
Checking I
Adding 1 to I
The if condition
All of them are done in every iteration.

calculating time complexity for insertion sort

I'm trying to understand the time complexity of insertion sort. I got stuck at while loop. I'm unable to understand how many times while loop executes
InsertionSort(A)
for j = 2 to A.length
key = A[j]
i = j - 1
while i>0 and A[i]>key
A[i+1] = A[i]
i = i - 1
A[i+1] = key
I know that for loop executes n+1 times and every statement in the loop execute n times
while loop also executes n times
But, what I don't understand is "How many times statements under while loop executes for both worst and best cases?"
In the worst case, A is sorted in descending order, which means that for the j'th entry, the inner loop will run j times (give or take a "+1" or "-1"...). Happily, there is a formula for that: as Gauss famously found out spontaneously and under duress, summing up all numbers from 1 to n yields a result of n*(n+1)/2.
As we only care about complexity and not actual values, we can leave the constant and multiplicative factors off and end up with O(n^2).
Tongue-in-cheek aside, the fact that there is a loop within a loop is a strong indication for O(n^2) when the inner loop count is bounded linearly - which it is here.
Best case, with A already sorted in ascending order, the inner loop will be entered not at all, and overall complexity will be O(n).
The average case depends heavily on what your expected "unorderedness" looks like. For example, the sort will behave greatly if your list is basically always sorted already, and there are only very few, very local switchups.

Why is this algorithm O(nlogn)?

I'm reading a book on algorithm analysis and have found an algorithm which I don't know how to get the time complexity of, although the book says that it is O(nlogn).
Here is the algorithm:
sum1=0;
for(k=1; k<=n; k*=2)
for(j=1; j<=n; j++)
sum1++;
Perhaps the easiest way to convince yourself of the O(n*lgn) running time is to run the algorithm on a sheet of paper. Consider what happens when n is 64. Then the outer loop variable k would take the following values:
1 2 4 8 16 32 64
The log_2(64) is 6, which is the number of terms above plus one. You can continue this line of reasoning to conclude that the outer loop will take O(lgn) running time.
The inner loop, which is completely independent of the outer loop, is O(n). Multiplying these two terms together yields O(lgn*n).
In your first loop for(k=1; k<=n; k*=2), variable k reaches the value of n in log n steps since you're doubling the value in each step.
The second loop for(j=1; j<=n; j++) is just a linear cycle, so requires n steps.
Therefore, total time is O(nlogn) since the loops are nested.
To add a bit of mathematical detail...
Let a be the number of times the outer loop for(k=1; k<=n; k*=2) runs. Then this loop will run 2^a times (Note the loop increment k*=2). So we have n = 2^a. Solve for a by taking base 2 log on both sides, then you will get a = log_2(n)
Since the inner loop runs n times, total is O(nlog_2(n)).
To add to #qwerty's answer, if a is the number of times the outer loop runs:
    k takes values 1, 2, 4, ..., 2^a and 2^a <= n
    Taking log on both sides: log_2(2^a) <= log_2(n), i.e. a <= log_2(n)
So the outer loop has a upper bound of log_2(n), i.e. it cannot run more than log_2(n) times.

Inconsistencies in Big-O Analysis of a Basic "Algorithm"

I recently learned about formal Big-O analysis of algorithms; however, I don't see why these 2 algorithms, which do virtually the same thing, would have drastically different running times. The algorithms both print numbers 0 up to n. I will write them in pseudocode:
Algorithm 1:
def countUp(int n){
for(int i = 0; i <= n; i++){
print(n);
}
}
Algorithm 2:
def countUp2(int n){
for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
... (continued so that this can print out all values 0 - Integer.MAX_VALUE)
for(int z = 0; z < 10; z++){
print("" + i + j + ... + k);
if(("" + i + j + k).stringToInt() == n){
quit();
}
}
}
}
}
So, the first algorithm runs in O(n) time, whereas the second algorithm (depending on the programming language) runs in something close to O(n^10). Is there anything with the code that causes this to happen, or is it simply the absurdity of my example that "breaks" the math?
In countUp, the loop hits all numbers in the range [0,n] once, thus resulting in a runtime of O(n).
In countUp2, you do somewhat the exact same thing, a bunch of times. The bounds on all your loops is 10.
Say you have 3 loop running with a bound of 10. So, outer loop does 10, inner does 10x10, innermost does 10x10x10. So, worst case your innermost loop will run 1000 times, which is essentially constant time. So, for n loops with bounds [0, 10), your runtime is 10^n which, again, can be called constant time, O(1), since it is not dependent on n for worst case analysis.
Assuming you can write enough loops and that the size of n is not a factor, then you would need a loop for every single digit of n. Number of digits in n is int(math.floor(math.log10(n))) + 1; lets call this dig. So, a more strict upper bound on the number of iterations would be 10^dig (which can be kinda reduced to O(n); proof is left to the reader as an exercise).
When analyzing the runtime of an algorithm, one key thing to look for is the loops. In algorithm 1, you have code that executes n times, making the runtime O(n). In algorithm 2, you have nested loops that each run 10 times, so you have a runtime of O(10^3). This is because your code runs the innermost loop 10 times for each run of the middle loop, which in turn runs 10 times for each run of the outermost loop. So the code runs 10x10x10 times. (This is purely an upper bound however, because your if-statement may end the algorithm before the looping is complete, depending on the value of n).
To count up to n in countUp2, then you need the same number of loops as the number of digits in n: so log(n) loops. Each loop can run 10 times, so the total number of iterations is 10^log(n) which is O(n).
The first runs in O(n log n) time, since print(n) outputs O(log n) digits.
The second program assumes an upper limit for n, so is trivially O(1). When we do complexity analysis, we assume a more abstract version of the programming language where (usually) integers are unbounded but arithmetic operations still perform in O(1). In your example you're mixing up the actual programming language (which has bounded integers) with this more abstract model (which doesn't). If you rewrite the program[*] so that is has a dynamically adjustable number of loops depending on n (so if your number n has k digits, then there's k+1 nested loops), then it does one iteration of the innermost code for each number from 0 up to the next power of 10 after n. The inner loop does O(log n) work[**] as it constructs the string, so overall this program too is O(n log n).
[*] you can't use for loops and variables to do this; you'd have to use recursion or something similar, and an array instead of the variables i, j, k, ..., z.
[**] that's assuming your programming language optimizes the addition of k length-1 strings so that it runs in O(k) time. The obvious string concatenation implementation would be O(k^2) time, meaning your second program would run in O(n(log n)^2) time.

Resources