I am working on a simple problem of finding height of a binary tree on HackerRank. My solution below passed all test cases and after some running code by hands, I believe it is an O(n) solution. Most other solutions I found online (I think) said it is O(n) as well, but I am unable to solve the recurrence relation to reach O(n) based on both solutions.
Assuming the tree is not balanced, below is my solution:
public static int height(Node root) {
// Write your code here.
if (root.left == null && root.right == null) {
return 0;
} else if (root.left == null && root.right != null) {
return 1 + height(root.right);
} else if (root.left != null && root.right == null) {
return 1 + height(root.left);
} else {
return 1 + Math.max(height(root.left), height(root.right));
}
}
I found that the number of functions called is almost exact the number of nodes, which means it should be O(n). Based on my last case, which I think is likely to be the worst runtime case when I need to call height(node) on both branches, I tried to derive the following recurrence relation
Let n be the number of nodes and k be the number of level
T(n) = 2 T(n/2)
Base Case: n = 0, n =1
T(0) = -1
T(1) = T(2^0)= 0
Recursive Case:
k = 1, T(2^1) = 2 * T(1)
k = 2, T(2^2) = 2 * T(2) = 2 * 2 * T(1) = 2^2 T(1)
k = 3, T(2^3) = 2^3 * T(1)
....
2^k = n=> k = logn => T(2^k) = 2^k * T(1) = 2^logn * T(1)
So to me apparently, the time complexity is O(2^logn), I am confused why people say it is O(n) ? I read this article (Is O(n) greater than O(2^log n)) and I guess it makes sense because O(n) > O(2^logn), but I have two questions:
Is my recurrence relation correct and the result correct ? If it is, why in reality ( I count the number of times function is called) I still get O(n) instead of O(2^logn) ?
How do you derive recurrence relation for a recursive function like this ? Do you take the worst case (in my case is last condition) and derive recurrence relation from that ?
As 2^log(n) = n based on the definition of the log function, you can find that both are the same. it means O(n) and O(2^log(n)) are equivalent.
Also if you need to find the height of the tree repeatedly, you can preprocess the tree and store the height of subtree for each node to find the height of the tree in O(1) after the preprocessing phase.
Related
I'm learning time efficiency of algorithms and have become stuck at trying to analyse recursive algorithms. I currently have an algorithm that just basically traverses a binary search tree and puts each node into an array.
placeIntoArray(root, array[]a, int i) {
if (root.left != null) {
i = placeIntoArray(root.left, a, i);
}
a[i] = root;
i++;
if (root.right != null) {
i = placeIntoArray(root.right, a, i);
}
return i;
}
If I had to guess I'd think it was in the class of O(n) since it's just touching each node of placing it into an array, but I'm not sure how to analyse it properly.. Any help would be appreciated
The time complexity of the problem for the array with the size of n is T(n) = T(number of the elements in the root.left) + T(number of the elements in the root.right) + c that c is constant. In two extreme scenarios, it would be T(n) = 2T(n/2) + c (completely balanced) which means T(n) = Theta(n) or T(n) = T(n-1) + T(1) + c (completely unblanced) that means T(n) = Theta(n). If you consider the other cases, you will find that T(n) = Theta(n).
int function(int n){
if (n<=1)
return 1;
else
return (2*function(n/2));
}
What is the recurrence relation T(n) for running time , and why ?
The complexity-function of this algorithm would be
T(n) = T(n / 2) + 1
T(1) = 1
Applying the master-theorem, we would get
a = 1
b = 2
c = 0 (1 = n^0)
log b(A) = log2(1) = 0 = 0 c, thus case 2
apply values and the result is O(log n).
As #guillaume already correctly stated, this can be solved a lot easier by using a linear function though.
You can calculate directly: it is the nearest 2^n, largest or equal.
You calculate L=log2(n), and you take 2^L, or 2^(L+1)
Complexity is O(log2 N) : log2 N operations.
int print4Subtree(struct Node *root) {
if (root == NULL)
return 0;
int l = print4Subtree(root->left);
int r = print4Subtree(root->right);
if ((l + r + 1) == 4)
printf("%d ", root->data);
return (l + r + 1); }
This algorithm/code finds number of subtrees having exactly 4 nodes in binary tree , it's works in bottom-up manner .
I know the time complexity of this code would be O(n) , and space complexity is O(log n) , since it's using recursion.
What will be recurrence relation for the code ?
I try to draw T(n) = 2T(n-1)+1 , which is obviously wrong !
You can only talk about recurrence relations in terms of n alone in cases where you know more about the structure of the tree, for instance:
Case 1: Every node has only one child meaning
T(n) = T(0) + T(n-1) + k.
Case 2: Subtrees at any level are balanced so that
T(n) = 2 T((n-1)/2) + k.
Both of these will result in O(n), but these two cases are only a very select minority of possible trees. For a more universal approach you have to use a formula like T(n) = T(a) + T(b), where a and b are an arbitrary division into sub-problems resulting from the structure of your tree. You can still establish results from this kind of formula using strong induction.
The following is the exact formula and approach I would use:
T(n) = nk + mnc, where mn ≤ n + 1. (Note: I am using k for overhead of recursive steps and c for overhead of base/null steps).
Base case (n=0):
For a null node T(0) = c so T(n) = kn + mnc ,
where mn = 1 ≤ n+1 = 1.
Inductive step (T(x) = xk + mxc for all x < n):
The sub_tree of size n has two sub-trees of sizes a and b (a or b may be 0) such that n = a + b + 1.
T(n) = T(a) + T(b) + k = ak + mac + bk + mbc + k = (a+b+1)k + (ma+mb)c = nk + mnc ,
where mn = ma + mb ≤ a + 1 + b + 1 = n + 1.
The reason for using mn is merely a formality to make the proof smoother, as the exact number of null cases is what is actually affected by the structure of tree (in the former case 2, it is log n). So T(n) is at best O(n) because of the nk term, and can be no worst than O(n) because of the bound on mnc.
Consider the element uniqueness problem, in which we are given a range, i, i + 1, . . . , j, of indices for an array, A, and we want to determine if the elements of this range, A[i], A[i+1], . . . , A[j], are all unique, that is, there is no repeated element in this group of array entries. Consider the following (inefficient) recursive algorithm.
public static boolean isUnique(int[] A, int start, int end) {
if (start >= end) return true; // the range is too small for repeats
// check recursively if first part of array A is unique
if (!isUnique(A,start,end-1) // there is duplicate in A[start],...,A[end-1]
return false;
// check recursively if second part of array A is unique
if (!isUnique(A,start+1,end) // there is duplicate in A[start+1],...,A[end]
return false;
return (A[start] != A[end]; // check if first and last are different
}
Let n denote the number of entries under consideration, that is, let n = end − start + 1. What is an upper is upper bound on the asymptotic running time of this code fragment for large n? Provide a brief and precise explanation.
(You lose marks if you do not explain.) To begin your explanation, you may say how many recursive calls the
algorithm will make before it terminates, and analyze the number of operations per invocation of this algorithm.
Alternatively, you may provide the recurrence characterizing the running time of this algorithm, and then solve it
using the iterative substitution technique?
This question is from a sample practise exam for an Algorithms class this is my current answer can some one please help verify if im on the right track
Answer:
The recurrence equation:
T(n) = 1 if n = 1,
T(n) = 2T(n-1) if n > 1
after solving using iterative substitution i got
2^k * T (n-k) and I solved this to O(2^(n-1)) and I simplified it O(2^n)
Your recurrence relation should be T(n) = 2T(n-1) + O(1) with T(1) = O(1). However this doesn't change the asymptotics, the solution is still T(n) = O(2^n). To see this you can expand the recurrence relation to get T(n) = O(1) + 2(O(1) + 2(O(1) + ...)) so you have T(n) = O(1) * (1 + 2 + 4 = ... + 2^n) = O(1) * (2^(n+1) - 1) = O(2^n).
I have a great Doubt in solving this recursive relation. Can anyone provide me a solution?
The relation:
T(n) = Sumation i=1 to N T(i) +1...,
What is the bigOh order?
Taking the first order difference allows you to get rid of the summation.
T(n) - T(n-1) = (Sum(1<=i<n: T(i)) + 1) - (Sum(1<=i<n-1: T(i)) + 1) = T(n-1)
Hence
T(n) = 2.T(n-1)
A recurrence relation describes a sequence of numbers. Early terms are specified explicitly and later terms are expressed as a function of their predecessors. As a trivial example, this recurrence describes the sequence 1, 2, 3, etc.:
void Sample(int n)
{
if (n > 0) {
Sample(n-1);
System.out.println('here');
} else {
return 1;
}
}
Sample(3);
Here, the first term is defined to be 1 and each subsequent term is one more than its predecessor. In order to analyze a recurrence relationship, we need to know execution time of each line of code, In above example:
void Sample(int n)
{
if (n > 0) {
// T(n-1) Sample(n-1);
// 1 System.out.println('here');
} else {
return 1;
}
}
We define T(n) as a:
For solving T(n)= T(n-1)+1, If we know what is T(n-1), then we can substitute it and get answer, Let's substitute it with n then, we will have:
T(n-1)= T(n-1-1)+1 => T(n-2)+1
//in continue
T(n)=[T(n-2)+1]+1 => T(n-2)+2
//and again
T(n)=[T(n-3)+2]+1 => T(n-3)+3
.
.
.
// if repeat it k times, while it ends
T(n)= T(n-k)+k
As you see it is increasing by 1 in each step, If we go k times, Then T(n)= T(n-k)+k. Now, we need to know the smallest value(when the the function will stop, always must be a point to stop). In this problem zero is end of recursive stack. sine we assume that we go k times to reach zero, the solution will be:
// if n-k is final step
n-k = 0 => n = k
// replace k with n
T(n)= T(n-n)+n => T(n)= T(0)+n;
// we know
T(0) = 1;
T(n) = 1+n => O(n)
The big O is n, It means this recursive algorithms in worst case goes n times.