I can't figure out the solution of this excercise:
Calculate the complexity of f(g(n))+g(f(n)) with g and f defined as follows:
int f(int x) {
if (x<=1) return 2;
int a = g(x) + 2*f(x/2);
return 1+ x + 2*a;
}
int g(int x) {
int b=0;
if (x<=1) return 5;
for (int i=1, i<=x*x;i++)
b+=i;
return b + g(x-1);
}
could anyone explain me how to get to the solution?
There are two separate steps to solving this problem. Firstly we must look at the time complexity of each function, and then the output complexity.
Time complexity
Since g is self-contained, let's look at it first.
The work done in g consists of:
x^2 executions of a loop
A recursive call with parameter x - 1
Hence one might write the time complexity recurrence relation as (using upper-case to distinguish it from the original function)
To solve it, repeatedly self-substitute to give a summation. This is the sum of the squares of natural numbers from 6 to x:
Where in the last step we used a standard result. And thus, since a is a constant:
Next, f:
One call to g(x)
One recursive call with parameter x / 2
Some constant amount of work
Using a similar method:
Applying the stopping condition:
Thus:
Since the exponential term 2^(-x^3) vanishes:
Output complexity
This is basically the same process as above, with slightly different recursion relations. I'll skip the details and just state the results (using lower case for output functions):
Thus the final time complexity of f(g(n)) + g(f(n)) is:
Which matches the result given by your source.
Related
Trying to analyze the runtime complexity of the following algorithm:
Problem: We have an m * n array A consisting of lower case letters and a target string s. The goal is to examine whether the target string appearing in A or not.
algorithm:
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(A[i][j] is equal to the starting character in s) search(i, j, s)
}
}
boolean search(int i, int j, target s){
if(the current position relative to s is the length of s) then we find the target
looping through the four possible directions starting from i, j: {p,q} = {i+1, j} or {i-1, j} or {i, j+1} or {i, j-1}, if the coordinate is never visited before
search(p, q, target s)
}
One runtime complexity analysis that I read is the following:
At each position in the array A, we are first presented with 4 possible directions to explore. After the first round, we are only given 3 possible choices because we can never go back. So the worst runtime complexity is O(m * n * 3**len(s))
However, I disagree with this analysis, because even though we are only presented with 3 possible choices each round, we do need to spend one operation to check whether that direction has been visited before or not. For instance, in java you probably just use a boolean array to track whether one spot has been visited before, so in order to know whether a spot has been visited or not, one needs a conditional check, and that costs one operation. The analysis I mentioned does not seem to take into account this.
What should be the runtime complexity?
update:
Let us suppose that the length of the target string is l and the runtime complexity at a given position in the matrix is T(l). Then we have:
T(l) = 4 T(l- 1) + 4 = 4(3T(l - 2) + 4) + 4 = 4(3( 3T(l -3) + 4) + 4)) + 4 = 4 * 3 ** (l - 1) + 4 + 4 *4 + 4 * 3 * 4 + ...
the +4 is coming from the fact that we are looping through four directions in each round besides recursively calling itself three times.
What should be the runtime complexity?
The mentioned analysis is correct and the complexity is indeed O(m * n * 3**len(s)).
For instance, in java you probably just use a boolean array to track whether one spot has been visited before, so in order to know whether a spot has been visited or not, one needs a conditional check, and that costs one operation.
That is correct and does not contradict the analysis.
The worst case we can construct is the matrix filled with only one letter a and a string aaaa....aaaax (many letters a and one x at the end). If m, n and len(s) are large enough, almost each call of the search function will generate 3 recursion calls of itself. Each of that calls will generate another 3 calls (which gives us total 9 calls of depth 2), each of them willl generate another 3 calls (which gives us total 27 calls of depth 3) and so on. Checking current string character, conditional checks, spawning a recursion are all O(1), so complexity of the whole search function is O(3**len(s)).
The solution is brute force. We have to touch each point on the board. That makes O(m*n) operation.
Now for each point, we have to run dfs() to check if the word exist. So we get
O(m * n * timeComplexityOf dfs)
this is a dfs written in python. Examine the time complexity
def dfs(r,c,i):
# O(1)
if i==len(word):
return True
# O(1)
# set is implemented as a hash table.
# So, time complexity of look up in a set is O(1)
if r<0 or c<0 or r>=ROWS or c>=COLS or word[i]!=board[r][c] or (r,c) in path_set:
return False
# O(1)
path.add((r,c))
# O(1)
res=(dfs(r+1,c,i+1) or
dfs(r-1,c,i+1) or
dfs(r,c+1,i+1) or
dfs(r,c-1,i+1))
# O(1)
path.remove((r,c))
return res
Since we dfs recursively calling itself, think about how many dfs calls will be on call stack. in worst case it will length of word. Thats why
O ( m * n * word.length)
Given this algorithm (a>0, b>0) :
while(a>=b){
k=1;
while(a>=k*b){
a = a - k*b;
k++;
}
}
My question : I have to find the time complexity of this algorithm and to do so, I must find the number of instructions but I couldn't find it. Is there a way to find this number and if not, how can I find its time complexity ?
What I have done : First of all I tried to find the number of iterations of the first loop and I found a pattern : a_i = a - (i(i+1)/2)*b where i is the number of iterations. I've spent hours doing some manipulations on it but I couldn't find anything relevant (I've found weird results like q² <= a/b < q²+q where q is the number of iterations).
You have correctly calculated that the value of a after the i-th iteration of the inner loop is:
Where a_j0 is the value of a at the start of the j-th outer loop. The stopping condition for the inner loop is:
Which can be solved as a quadratic inequality:
Therefore the inner loop is approximately O(sqrt(a_j0 / b)). The next starting value of a satisfies:
Scaling roughly as sqrt(2b * a_j0). It would be quite tedious to compute the time complexity exactly, so let's apply the above approximations from here on:
Where a_n replaces a_j0, and t_n is the run-time of the inner loop – and of course the total time complexity is just the sum of t_n. Note that the first term is given by n = 1, and that the input value of a is defined to be a_0.
Before directly solving this recurrence, note that since the second term t_2 is already proportional to the square root of the first t_1, the latter dominates all other terms in the sum.
The total time complexity is therefore just O(sqrt(a / b)).
Update: numerical tests.
Note that, since all changes in the value of a are proportional to b, and all loop conditions are also proportional to b, the function can be "normalized" by setting b = 1 and only varying a.
Javascript test function, which measures the number of times that the inner loop executes:
function T(n)
{
let t = 0, k = 0;
while (n >= 1) {
k = 1;
while (n >= k) {
n -= k;
k++; t++;
}
}
return t;
}
Plot of sqrt(n) against T(n):
A convincing straight line which confirms that the time complexity is indeed half-power.
I’m studying algorithm - time complexity and recursion.
I’m actually ok with solving recursion, cuz it’s simple math. But code part is the problem.
For example, This is the problem I’ve brought :
https://brilliant.org/practice/big-o-notation/?problem=complexityrun-time-analysis-2-2
public int P(int x , int n){
if (n == 0){
return 1;
}
if (n % 2 == 1){
int y = P(x, (n - 1) / 2);
return x * y * y;
}
else{
int y = P(x, n / 2);
return y * y;
}
}
It is a simple power function. T(n)=O(g(n)) is running time of this function for large, and I have to find it.
The solution says,
“When the power is odd an extra multiplication operation is performed. To work out time complexity, let us first look at the worst scenario, meaning let us assume that one additional multiplication operation is needed.”
However, I do not understand the next part, the solution says that :
Recursion relation is
T(n) = T(n/2) + 3, T(1)=1
1) Why is the constant part 3?
if (n % 2 == 1){
int y = P(x, (n - 1) / 2);
return x * y * y;
}
2) I actually don’t get exactly why T(1)=1 also.
I’m puzzled with.. which operations should we consider while calculating time complexity?
For example, T(1)=1 part must be related with
if (n == 0){
return 1;
}
if (n % 2 == 1){
int y = P(x, (n - 1) / 2);
return x * y * y;
}
This part, and I want to ask whether T(1)=1 comes from if statement/assign statement/return statement..
I understand afterwards, solving the recursion relation above, but I’m stuck with the recursion relation itself.
Please help me algo gurus..
which operations should we consider while calculating time complexity?
The answer will disappoint you a bit: it doesn't matter what operations you count. That's why we use big-Oh in analysing algorithms and expressing their time/memory requirements. It is an asymptotic notation that describes what happens to the algorithm for large values of n. By the definition of Big-Oh, we can say that both 1/2n^2 and 10n^2+6n+100 are O(n^2), even if they are not the same function. Counting all the operations, will just increase some constant factors, and that's why it doesn't really matter which ones you count.
By the above, the constants are simply O(1). This disregards details, since both 10 and 10000 are O(1), for example.
One could argue that specifying the exact number of operations in the expression T(n) = T(n/2) + 3 is not very correct, since there is no definition for what an operation is, and moreover the same operation might take a different amount of time on different computers, so exactly counting the number of operations is a bit meaningless at best and simply wrong at worst. A better way of saying it is T(n) = T(n/2) + O(1).
T(1)=1 represents the base case, which is solved in constant time (read: a constant number of operations at each time). Again, a better (more formal) way of saying that is T(1)=O(1).
In my algorithm and data structures class we were given a few recurrence relations either to solve or that we can see the complexity of an algorithm.
At first, I thought that the mere purpose of these relations is to jot down the complexity of a recursive divide-and-conquer algorithm. Then I came across a question in the MIT assignments, where one is asked to provide a recurrence relation for an iterative algorithm.
How would I actually come up with a recurrence relation myself, given some code? What are the necessary steps?
Is it actually correct that I can jot down any case i.e. worst, best, average case with such a relation?
Could possibly someone give a simple example on how a piece of code is turned into a recurrence relation?
Cheers,
Andrew
Okay, so in algorithm analysis, a recurrence relation is a function relating the amount of work needed to solve a problem of size n to that needed to solve smaller problems (this is closely related to its meaning in math).
For example, consider a Fibonacci function below:
Fib(a)
{
if(a==1 || a==0)
return 1;
return Fib(a-1) + Fib(a-2);
}
This does three operations (comparison, comparison, addition), and also calls itself recursively. So the recurrence relation is T(n) = 3 + T(n-1) + T(n-2). To solve this, you would use the iterative method: start expanding the terms until you find the pattern. For this example, you would expand T(n-1) to get T(n) = 6 + 2*T(n-2) + T(n-3). Then expand T(n-2) to get T(n) = 12 + 3*T(n-3) + 2*T(n-4). One more time, expand T(n-3) to get T(n) = 21 + 5*T(n-4) + 3*T(n-5). Notice that the coefficient of the first T term is following the Fibonacci numbers, and the constant term is the sum of them times three: looking it up, that is 3*(Fib(n+2)-1). More importantly, we notice that the sequence increases exponentially; that is, the complexity of the algorithm is O(2n).
Then consider this function for merge sort:
Merge(ary)
{
ary_start = Merge(ary[0:n/2]);
ary_end = Merge(ary[n/2:n]);
return MergeArrays(ary_start, ary_end);
}
This function calls itself on half the input twice, then merges the two halves (using O(n) work). That is, T(n) = T(n/2) + T(n/2) + O(n). To solve recurrence relations of this type, you should use the Master Theorem. By this theorem, this expands to T(n) = O(n log n).
Finally, consider this function to calculate Fibonacci:
Fib2(n)
{
two = one = 1;
for(i from 2 to n)
{
temp = two + one;
one = two;
two = temp;
}
return two;
}
This function calls itself no times, and it iterates O(n) times. Therefore, its recurrence relation is T(n) = O(n). This is the case you asked about. It is a special case of recurrence relations with no recurrence; therefore, it is very easy to solve.
To find the running time of an algorithm we need to firstly able to write an expression for the algorithm and that expression tells the running time for each step. So you need to walk through each of the steps of an algorithm to find the expression.
For example, suppose we defined a predicate, isSorted, which would take as input an array a and the size, n, of the array and would return true if and only if the array was sorted in increasing order.
bool isSorted(int *a, int n) {
if (n == 1)
return true; // a 1-element array is always sorted
for (int i = 0; i < n-1; i++) {
if (a[i] > a[i+1]) // found two adjacent elements out of order
return false;
}
return true; // everything's in sorted order
}
Clearly, the size of the input here will simply be n, the size of the array. How many steps will be performed in the worst case, for input n?
The first if statement counts as 1 step
The for loop will execute n−1 times in the worst case (assuming the internal test doesn't kick us out), for a total time of n−1 for the loop test and the increment of the index.
Inside the loop, there's another if statement which will be executed once per iteration for a total of n−1 time, at worst.
The last return will be executed once.
So, in the worst case, we'll have done 1+(n−1)+(n−1)+1
computations, for a total run time T(n)≤1+(n−1)+(n−1)+1=2n and so we have the timing function T(n)=O(n).
So in brief what we have done is-->>
1.For a parameter 'n' which gives the size of the input we assume that each simple statements that are executed once will take constant time,for simplicity assume one
2.The iterative statements like loops and inside body will take variable time depending upon the input.
Which has solution T(n)=O(n), just as with the non-recursive version, as it happens.
3.So your task is to go step by step and write down the function in terms of n to calulate the time complexity
For recursive algorithms, you do the same thing, only this time you add the time taken by each recursive call, expressed as a function of the time it takes on its input.
For example, let's rewrite, isSorted as a recursive algorithm:
bool isSorted(int *a, int n) {
if (n == 1)
return true;
if (a[n-2] > a[n-1]) // are the last two elements out of order?
return false;
else
return isSorted(a, n-1); // is the initial part of the array sorted?
}
In this case we still walk through the algorithm, counting: 1 step for the first if plus 1 step for the second if, plus the time isSorted will take on an input of size n−1, which will be T(n−1), giving a recurrence relation
T(n)≤1+1+T(n−1)=T(n−1)+O(1)
Which has solution T(n)=O(n), just as with the non-recursive version, as it happens.
Simple Enough!! Practice More to write the recurrence relation of various algorithms keeping in mind how much time each step will be executed in algorithm
How to calculate time complexity of the following algorithm. I tried but I am getting confused because recursive calls.
power (real x, positive integer n)
//comment : This algorithm returns xn, taking x and n as input
{
if n=1 then
return x;
y = power(x, |n/2|)
if n id odd then
return y*y*x //comment : returning the product of y2 and x
else
return y * y //comment : returning y2
}
can some one explain in simple steps.
To figure out the time complexity of a recursive function you need to calculate the number of recursive calls that is going to be made in terms of some input variable N.
In this case, each call makes at most one recursive invocation. The number of invocations is on the order of O(log2N), because each invocation decreases N in half.
The rest of the body of the recursive function is O(1), because it does not depend on N. Therefore, your function has time complexity of O(log2N).
Each call is considered a constant time operation, and how many times will it recurse is equal to how many times can you do n/2 before n = 1, which is at most log2(n) times. Therefore the worst case running time is O(log2n).