Diameter of Binary Tree - Algorithm Complexity - algorithm

In another question about finding an algorithm to compute the diameter of a binary tree the following code is provided as a possible answer to the problem.
public static int getDiameter(BinaryTreeNode root) {
if (root == null)
return 0;
int rootDiameter = getHeight(root.getLeft()) + getHeight(root.getRight()) + 1;
int leftDiameter = getDiameter(root.getLeft());
int rightDiameter = getDiameter(root.getRight());
return Math.max(rootDiameter, Math.max(leftDiameter, rightDiameter));
}
public static int getHeight(BinaryTreeNode root) {
if (root == null)
return 0;
return Math.max(getHeight(root.getLeft()), getHeight(root.getRight())) + 1;
}
In the comments section it's being said that the time complexity of the above code is O(n^2). At a given call of the getDiameter function, the getHeight and the getDiameter functions are called for the left and right subtrees.
Let's consider the average case of a binary tree. Height can be computed at Θ(n) time (true for worst case too). So how do we compute the time complexity for the getDiameter function?
My two theories
Τ(n) = 4T(n/2) + Θ(1) = Θ(n^2), height computation is considered
(same?) subproblem.
T(n) = 2T(n/2) + n + Θ(1) = Θ(nlogn), n = 2*n/2 for height computation?
Thank you for your time and effort!

One point of confusion is that you think the binary tree is balanced. Actually, it can be a line. In this case, we need n operations from the root to the leaf to find the height, n - 1 from the root's child to the leaf and so on. This gives O(n^2) operations to find the height alone for all nodes.
The algorithm could be optimised if the height of each node was calculated independently, before finding the diameter. Then we would spend O(n) time for finding all heights. Then the complexity of finding the diameter would be of the following type:
T(n) = T(a) + T(n - 1 - a) + 1
where a is the size of the left subtree. This relation would give linear time for finding diameter also. So the total time would be linear.

Related

Complexity of the following recursive code

Trying to figure out why complexity is O(n) for this code:
int sum(Node node) {
if (node == null) {
return 0;
}
return sum(node.left) + node.value + sum(node.right);
}
Node is:
class Node {
int value;
Node left;
Node right;
}
This is from CCI book. Shouldn't it be O(2^n) since it iterates through each node?
Yet this one is O(2^n), which is clear to me why:
int f(int n) {
if (n <= 1) {
return 1;
}
return f(n - 1) + f(n - 1);
}
Thanks for help.
An algorithm is said to take linear time, or O(n) time, if its time
complexity is O(n). Informally, this means that for large enough input
sizes the running time increases linearly with the size of the input.
For example, a procedure that adds up all elements of a list requires
time proportional to the length of the list.
From Wikipedia
It is very reasonable that the alogrithm complexity is O(n) since the recursive function numbers of calls is proportional to the number of items in the tree, there is n items in the tree and we will pass each item only once, this sounds very linear realtion to me.
In contrast to the other algorithm which is very similar to recursive Fibonacci Sequence algorithm, it this algorithm we will pass each number from 1 until n much more times than once and not linear proportional to n either, this explains why it has O(2^n) complexity.

how to find the position of right most node in last level of complete binary tree?

I am doing a problem in binary trees, and when I came across a problem find the right most node in the last level of a complete binary tree and the issue here is we have to do it in O(n) time which was a stopping point, Doing it in O(n) is simple by traversing all the elements, but is there a way to do this in any complexity less than O(n), I have browsed through internet a lot, and I couldn't get anything regarding the thing.
Thanks in advance.
Yes, you can do it in O(log(n)^2) by doing a variation of binary search.
This can be done by first going to the leftest element1, then to the 2nd leftest element, then to the 4th leftest element, 8th ,... until you find there is no such element.
Let's say the last element you found was the ith, and the first you didn't was 2i.
Now you can simply do a binary search over that range.
This is O(log(n/2)) = O(logn) total iterations, and since each iteration is going down the entire tree, it's total of O(log(n)^2) time.
(1) In here and the followings, the "x leftest element" is referring only to the nodes in the deepest level of the tree.
I assume that you know the number of nodes. Let n such number.
In a complete binary tree, a level i has twice the number of nodes than the level i - 1.
So, you could iteratively divide n between 2. If there remainder then n is a right child; otherwise, is a left child. You store into a sequence, preferably a stack, whether there is remainder or not.
Some such as:
Stack<char> s;
while (n > 1)
{
if (n % 2 == 0)
s.push('L');
else
s.push('R');
n = n/2; // n would int so division is floor
}
When the while finishes, the stack contains the path to the rightmost node.
The number of times that the while is executed is log_2(n).
This is the recursive solution with time complexity O(lg n* lg n) and O(lg n) space complexity (considering stack storage space).
Space complexity can be reduced to O(1) using Iterative version of the below code.
// helper function
int getLeftHeight(TreeNode * node) {
int c = 0;
while (node) {
c++;
node = node -> left;
}
return c;
}
int getRightMostElement(TreeNode * node) {
int h = getLeftHeight(node);
// base case will reach when RightMostElement which is our ans is found
if (h == 1)
return node -> val;
// ans lies in rightsubtree
else if ((h - 1) == getLeftHeight(node -> right))
return getRightMostElement(node -> right);
// ans lies in left subtree
else getRightMostElement(node -> left);
}
Time Complexity derivation -
At each recursion step, we are considering either left subtree or right subtree i.e. n/2 elements for maximum height (lg n) function calls,
calculating height takes lg n time -
T(n) = T(n/2) + c1 lgn
= T(n/4) + c1 lgn + c2 (lgn - 1)
= ...
= T(1) + c [lgn + (lgn-1) + (lgn-2) + ... + 1]
= O(lgn*lgn)
Since it's a complete binary tree, going over all the right nodes until you reach the leaves will take O(logN), not O(N). In regular binary tree it takes O(N) because in the worst case all the nodes are lined up to the right, but since it's a complete binary tree, it can't be

Time complexity for finding the diameter of a binary tree

I have seen various posts here that computes the diameter of a binary tree. One such solution can be found here (Look at the accepted solution, NOT the code highlighted in the problem).
I'm confused why the time complexity of the code would be O(n^2). I don't see how traversing the nodes of a tree twice (once for the height (via getHeight()) and once for the diameter (via getDiameter()) would be n^2 instead of n+n which is 2n. Any help would be appreciated.
As you mentioned, the time complexity of getHeight() is O(n).
For each node, the function getHeight() is called. So the complexity for a single node is O(n). Hence the complexity for the entire algorithm (for all nodes) is O(n*n).
It should be O(N) to calculate the height of every subtree rooted at every node, you only have to traverse the tree one time using an in-order traversal.
int treeHeight(root)
{
if(root == null) return -1;
root->height = max(treeHeight(root->rChild),treeHeight(root->lChild)) + 1;
return root->height;
}
This will visit each node 1 time, so has order O(N).
Combine this with the result from the linked source, and you will be able to determine which 2 nodes have the longest path between in at worst another traversal.
Indeed this describes the way to do it in O(N)
The different between this solution (the optimized one) and the referenced one is that the referenced solution re-computes tree height every time after shrinking the search size by only 1 node (the root node). Thus from above the complexity will be O(N + (N - 1) + ... + 1).
The sum
1 + 2 + ... + N
is equal to
= N(N + 1)/2
And so the complexity of sum of all the operations from the repeated calls to getHeight will be O(N^2)
For completeness sake, conversely, the optimized solution getHeight() will have complexity O(1) after the pre computation because each node will store the value as a data member of the node.
All subtree heights may be precalculated (using O(n) time), so what total time complexity of finding the diameter would be O(n).

time complexity of the following algorithm

Suppose a Node (in a BST) is defined as follows (ignore all the setters/getters/inits).
class Node
{
Node parent;
Node leftChild;
Node rightChild;
int value;
// ... other stuff
}
Given some a reference to some Node in a BST (called startNode) and another Node (called target), one is to check whether the tree containing startNode has any node whose value is equal to target.value.
I have two algorithms to do this:
Algorithm #1:
- From `startNode`, trace the way to the root node (using the `Node.parent` reference) : O(n)
- From the root node, do a regular binary search for the target : O(log(n))
T(n) = O(log(n) + n)
Algorithm #2: Basically perform a DFS
(Psuedo-code only)
current_node = startnode
While the root has not been reached
go up one level from the current_node
perform a binary-search from this node downward (excluding the branch from which we just go up)
What is the time-complexity of this algorithm?
The naive answer would be O(n * log(n)), where n is for the while loop, as there are at most n nodes, and log(n) is for the binary-search. But obviously, that is way-overestimating!
The best (partial) answer I could come up with was:
Suppose each sub-branch has some m_i nodes and that there are k
sub-branches.
In other words, k is the number of nodes between startNode and the root node
The total time would be
.
T(n) = log(m1) + log(m2) + ... + log(mk)
= log(m1 * m2 * ... * mk)
Where m1 + m2 + ... + mk = n (the total number of nodes in the tree)
(This is the best estimation I could get as I forgot most of my maths to do any better!)
So I have two questions:
0) What is the time-complexity of algorithm #2 in terms of n
1) Which algorithm does better in term of time-complexity?
Ok, after digging through my old Maths books, I was able to find that the upper bound of a product of k numbers whose sum is n is p <= (n /k) ^k.
With that said, the T(n) function would become:
T(n) = O(f(n, k))
Where
f(n, k) = log((n/k)^k)
= k * log(n/k)
= k * log(n) - k * log(k)
(Remember, k is the number nodes between the startNode and the root, while n is the total number of node)
How would I go from here? (ie., how do I simplify the f(n, k)? Or is that good enough for Big-O analysis? )

BinaryTree functions - complexity

I am just writing down different function, which are operation what can be done on a binary tree.
I am wondering what is the running time of this function, trying to get rid with them:
getMaxDepth(Tree) //What can be the time complexity here?
if Tree.root = NIL return 0 // BaseCase
leftDepth := 1 + getMaxDepth(Tree.root.left)
rightDepth := 1 + getMaxDepth(Tree.root.right)
if leftDepth > rightDepth then return leftDepth;
else return rightDepth;
internalNodeCount(Node n) // And here?
if isLeaf(n) then return 0
return 1 + internalNodeCount(n.left) + internalNodeCount(n.right)
isLeaf(Node n)
return n=NIL OR (n.left=NIL AND n.right=nil);
GetMaxDepth I assume the time complexity is O(n) because I need to traverse the whole tree recursively for ever node....what can be a good explanation?
InternalNodeCount I guess it is the same complexity O(n) for the same reason.....
From what I understood it looks like you are looking for some proof.
For getMaxDepth here is the explanation:
T(1) = c1
T(n) = T(k) + T(n-k-1) + c2
where
T(n) = Time to process tree of n nodes
n = number of nodes
k = nodes in left subtree
n-k-1 = nodes in right subtree
c1, c2 = constants (not dependent upon n)
(Time to calculate the depth of the tree from given left and right subtree depth)
The same could be applied to internalNode too excepts the contants would be different.

Resources