Average complexity of an algorithm - algorithm

My teacher gave me the below code to find out his average complexity:
int function(int a[], int n)
{
int k=0;
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
k=k+((a[i]*a[i]+a[j]*a[j])%5==0)
return k;
}
void main()
{
int vector={0,1,2,3,4,5,6,7,8,9}
int a=function(vector, 10);
printf( "%d\n", a);
}
By unrowling the loops I found out that the code executes n*(n+1)/2 times and I conclude that the worst case is O(n^2) because exists n*(n+1)/2 < c*n^2 for n>n0 .I know that the definition of average complexity is quite similar, but I found quite difficult to calculate it.I want to know what is the complexity in this case and if there are standardized methods for calculating these type of problems
( eg : nested loops with dependencies between the iterators).

In computational complexity theory, the average-case complexity of an algorithm is the amount of some computational resource (typically time) used by the algorithm, averaged over all possible inputs see here for definition.
In your case, you have already figured out that your program will execute for n*(n+1)/2 (for a given n) times. Then you can think: what if n = 1, 2, 3, ...? You only need to add up all those values using your formula and take an average. It is easy to get the O(n^2) solution.

In average case analysis, we take all possible inputs and calculate computing time for all of the inputs. Sum all the calculated values and divide the sum by total number of inputs.
There is only one possiblity in your algorithm. For all inputs, your algorithm runs in O(n*(n+1)/2) time.
Average time complexity is O(n*(n+1)/2) = O(n^2).

Related

Search a Sorted Array for First Occurrence of K

I'm trying to solve question 11.1 in Elements of Programming Interviews (EPI) in Java: Search a Sorted Array for First Occurrence of K.
The problem description from the book:
Write a method that takes a sorted array and a key and returns the index of the first occurrence of that key in the array.
The solution they provide in the book is a modified binary search algorithm that runs in O(logn) time. I wrote my own algorithm also based on a modified binary search algorithm with a slight difference - it uses recursion. The problem is I don't know how to determine the time complexity of my algorithm - my best guess is that it will run in O(logn) time because each time the function is called it reduces the size of the candidate values by half. I've tested my algorithm against the 314 EPI test cases that are provided by the EPI Judge so I know it works, I just don't know the time complexity - here is the code:
public static int searchFirstOfKUtility(List<Integer> A, int k, int Lower, int Upper, Integer Index)
{
while(Lower<=Upper){
int M = Lower + (Upper-Lower)/2;
if(A.get(M)<k)
Lower = M+1;
else if(A.get(M) == k){
Index = M;
if(Lower!=Upper)
Index = searchFirstOfKUtility(A, k, Lower, M-1, Index);
return Index;
}
else
Upper=M-1;
}
return Index;
}
Here is the code that the tests cases call to exercise my function:
public static int searchFirstOfK(List<Integer> A, int k) {
Integer foundKey = -1;
return searchFirstOfKUtility(A, k, 0, A.size()-1, foundKey);
}
So, can anyone tell me what the time complexity of my algorithm would be?
Assuming that passing arguments is O(1) instead of O(n), performance is O(log(n)).
The usual theoretical approach for analyzing recursion is calling the Master Theorem. It is to say that if the performance of a recursive algorithm follows a relation:
T(n) = a T(n/b) + f(n)
then there are 3 cases. In plain English they correspond to:
Performance is dominated by all the calls at the bottom of the recursion, so is proportional to how many of those there are.
Performance is equal between each level of recursion, and so is proportional to how many levels of recursion there are, times the cost of any layer of recursion.
Performance is dominated by the work done in the very first call, and so is proportional to f(n).
You are in case 2. Each recursive call costs the same, and so performance is dominated by the fact that there are O(log(n)) levels of recursion times the cost of each level. Assuming that passing a fixed number of arguments is O(1), that will indeed be O(log(n)).
Note that this assumption is true for Java because you don't make a complete copy of the array before passing it. But it is important to be aware that it is not true in all languages. For example I recently did a bunch of work in PL/pgSQL, and there arrays are passed by value. Meaning that your algorithm would have been O(n log(n)).

Reverse an array-run time

The following code reverses an array.What is its runtime ?
My heart says it is O(n/2), but my friend says O(n). which is correct? please answer with reason. thank you so much.
void reverse(int[] array) {
for (inti = 0; i < array.length / 2; i++) {
int other = array.length - i - 1;
int temp = array[i];
array[i] = array[other];
array[other] = temp;
}
}
Big-O complexity captures how the run-time scales with n as n gets arbitrarily large. It isn't a direct measure of performance. f(n) = 1000n and f(n) = n/128 + 10^100 are both O(n) because they both scale linearly with n even though the first scales much more quickly than the second, and the second is actually prohibitively slow for all n because of the large constant cost. Nonetheless, they have the same complexity class. For these sorts of reasons, if you want to differentiate actual performance between algorithms or define the performance of any particular algorithm (rather than how performance scales with n) asymptotic complexity is not the best tool. If you want to measure performance, you can count the exact number of operations performed by the algorithm, or better yet, provide a representative set of inputs and just measure the execution time on those inputs.
As for the particular problem, yes, the for loop runs n/2 times, but you also do some constant number of operations, c, in each of those loops (subtractions, array accesses, variable assignments, conditional check on i). Maybe c=10, it's not really important to count precisely to determine the complexity class, just to know that it's constant. The run-time is then f(n)=c*n/2, which is O(n): the fact that you only do n/2 for-loops doesn't change the complexity class.

Time Complexity of a recursive function where the base case isn't O(1)

Most recursive functions I have seen being asked about (e.g. Fibonacci or Hanoi) have had O(1) returns, but what would the time complexity be if it wasn't O(1) but O(n) instead?
For example, a recursive Fibonacci with O(n) base case:
class fibonacci {
static int fib(int n) {
if (n <= 1)
for (int i=0;i<n;i++) {
// something
}
return n;
return fib(n-1) + fib(n-2);
}
public static void main (String args[])
{
int n = 9;
System.out.println(fib(n));
}
}
The base case for the function that you’ve written here actually still has time complexity O(1). The reason for this is that if the base case triggers here, then n ≤ 1, so the for loop here will run at most once.
Because so many base cases trigger when the input size is small, it’s comparatively rare to get a base case whose runtime is, say, O(n) when the input to the algorithm has size n. This would mean that the base case is independent of the array size, which can happen but is somewhat unusual.
A more common occurrence - albeit one I think is still pretty uncommon - would be for a recursive function to have two different parameters to it (say, n and k), where the recursion reduces n but leaves k unmodified. For example, imagine taking the code you have here and replacing the for loop on n in the base case with a for loop on k in the base case. What happens then?
This turns out to be an interesting question. In the case of this particular problem, it means that the total work done will be given by O(k) times the number of base cases triggered, plus O(1) times the number of non-base-case recursive calls. For the Fibonacci recursion, the number of base cases triggered computing Fn is Fn+1 and there are (Fn+1 - 1) non-base-case calls, so the overall runtime would be Θ(k Fn+1 + Fn+1) = Θ(k φn). For the Towers of Hanoi, you’d similarly see a scaling effect where the overall runtime would be Θ(k 2n). But for other recursive functions the runtime might vary in different ways, depending on how those functions were structured.
Hope this helps!

when we do the sum of n numbers using for loop **ex.for(i=1;i<=n;i++)**

when we do the sum of n numbers using for loop for(i=1;i<=n;i++)complexity of this is O(n), but if we do this same computation using the formula of arithmetic/geometric progression series n(n-1)/2 that time if we compute the time complexity, its O(n^2). How ? please solve my doubt.
You are confused by what the numbers are representing.
Basically we are counting the # of steps when we talking about complexity.
n(n+1)/2 is the answer of Summation(1..n), that's correct, but different way take different # of steps to compute it, and we are counting the # of such steps.
Compare the following:
int ans = 0;
for(int i=1; i<=n;i++) ans += i;
// this use n steps only
int ans2 = 0;
ans2 = n*(n+1)/2;
// this use 1 step!!
int ans3 = 0;
for(int i=1, mx = n*(n+1)/2; i<=mx; i++) ans3++;
// this takes n*(n+1)/2 step
// You were thinking the formula would look like this when translated into code!
All three answers give the same value!
So, you can see only the first method & the third method (which is of course not practical at all) is affected by n, different n will cause them take different steps, while the second method which uses the formula, always take 1 step no matter what is n
Being said, if you know the formula beforehand, it is always the best you just compute the answer directly with the formula
Your second formula has O(1) complexity, that is, it runs in constant time, independent of n.
There's no contradiction. The complexity is a measure of how long the algorithm takes to run. Different algorithms can compute the same result at different speeds.
[BTW the correct formula is n*(n+1)/2.]
Edit: Perhaps your confusion has to do with an algorithm that takes n*(n+1)/2 steps, which is (n^2 + n)/2 steps. We call that O(n^2) because it grows essentially (asymptotically) as n^2 when n gets large. That is, it grows on the order of n^2, the high order term of the polynomial.

Tracking Runtime Complexity

What is the best way to calculate runtime complexity for any method? It's easy to do that for non-recursive methods, like bubblesort
outer-for loop
{
inner-for loop
{
compare and exchange
}
}
To check, the best way is to put a counter in the inner-most loop. But, when the method is recursive, where should I put the counter, for instance merge sort,
sort(int[] array){
left = first-half
right = second-half
sort(left);
sort(right);
ret merge(left, right);
}
merge(int[] left, right)
{
count = length(left + right);
int[] result;
loop-count-times
{
compare and put in result;
}
return result;
}
Since this is merge sort, the big(o) is o(n log n), so an array of 100 ints should return a big-o of 200 exactly. Where will the counter go? If I put it at the top of sort(..), I get an average of 250, 280, 300, which should be wrong. What is the best place for this counter?
references:http://en.wikipedia.org/wiki/Mergesort
Thanks.
Since this is merge sort, the big(o) is o(n log n), so an array of 100 ints should return a big-o of 200 exactly.
Not even close to right.
Computational complexity denoted using the big Ordo-notation does not tell you how many steps/computational operations will be executed exactly. There's a reason it's called asymptotic and not identical complexity: it only gives you a function that approaches (more precisely, gives a higher bound on) the running time of the algorithm with regards to the size of the input.
So O(n log n) doesn't mean that for 100 elements, 200 operations will be performed (how come, by the way, that the base of the logarithm must be 10?), it tells you that if you increase the size of your input, the (average-case) running time will be proportional to the number of pieces of input data added, multiplied by the logarithm of the number of this additional data.
To the point: if you want to count the number of calls to a recursive function, you should put the counter in as an argument, like this:
void merge_sort(int array[], size_t length, int *counter)
{
(*counter)++;
// apply the algorithm to `array`:
merge_sort(array, length, counter);
}
and call it like this:
int num_calls = 0;
merge_sort(array, sizeof(array) / sizeof(array[0]), &num_calls);
printf("Called %d times\n", num_calls);
I think you have slightly misunderstood the concept of Big-O notation. If the complexity is O(n log n) and the value of n is 100, there is no strict rule that the program should execute exactly in Big-O of 200. It only gives us an upper bound. For example consider selection sort with an O(n2) complexity. Even if n is 100 the counter set inside the inner loop will not give you 1002 as result if the list is already sorted. So in your case what you get as answer (250, 280, 300, etc.) is perfectly valid. Because all the answers are limited by k times n log n, where k is an arbitrary constant.

Resources