Lowest Common Ancestor implementations - what's the difference? - algorithm

I've been reading about the Lowest Common Ancestor algorithm on top coder and I can't understand why the RMQ algorithm is involved - the solution listed there is insanely complicated and has the following properties:
O(sqrt(n)) time complexity for searches, O(n) precalculation time complexity
O(n) space complexity for storing parents of each node
O(n) space complexity again, for storing precalculations of each node
My solution: given 2 integer values, find the nodes through a simple preorder traversal. Take one of the nodes and go up the tree and store the path into a Set. Take the other node and go up the tree and check each node as I go up: if the node is in the Set, stop and return the LCA. Full implementation.
O(n) time complexity for finding each of the 2 nodes, given the values (because it's a regular tree, not a BST -
O(log n) space complexity for storing the path into the Set
O(log n) time complexity for going up the tree with the second node
So given these two choices, is the algorithm on Top Coder better and if yes, why? That's what I can't understand. I thought O(log n) is better than O(sqrt(n)).
public class LCA {
private class Node {
int data;
Node[] children = new Node[0];
Node parent;
public Node() {
}
public Node(int v) {
data = v;
}
#Override
public boolean equals(Object other) {
if (this.data == ((Node) other).data) {
return true;
}
return false;
}
}
private Node root;
public LCA() {
root = new Node(3);
root.children = new Node[4];
root.children[0] = new Node(15);
root.children[0].parent = root;
root.children[1] = new Node(40);
root.children[1].parent = root;
root.children[2] = new Node(100);
root.children[2].parent = root;
root.children[3] = new Node(10);
root.children[3].parent = root;
root.children[0].children = new Node[3];
root.children[0].children[0] = new Node(22);
root.children[0].children[0].parent = root.children[0];
root.children[0].children[1] = new Node(11);
root.children[0].children[1].parent = root.children[0];
root.children[0].children[2] = new Node(99);
root.children[0].children[2].parent = root.children[0];
root.children[2].children = new Node[2];
root.children[2].children[0] = new Node(120);
root.children[2].children[0].parent = root.children[2];
root.children[2].children[1] = new Node(33);
root.children[2].children[1].parent = root.children[2];
root.children[3].children = new Node[4];
root.children[3].children[0] = new Node(51);
root.children[3].children[0].parent = root.children[3];
root.children[3].children[1] = new Node(52);
root.children[3].children[1].parent = root.children[3];
root.children[3].children[2] = new Node(53);
root.children[3].children[2].parent = root.children[3];
root.children[3].children[3] = new Node(54);
root.children[3].children[3].parent = root.children[3];
root.children[3].children[0].children = new Node[2];
root.children[3].children[0].children[0] = new Node(25);
root.children[3].children[0].children[0].parent = root.children[3].children[0];
root.children[3].children[0].children[1] = new Node(26);
root.children[3].children[0].children[1].parent = root.children[3].children[0];
root.children[3].children[3].children = new Node[1];
root.children[3].children[3].children[0] = new Node(27);
root.children[3].children[3].children[0].parent = root.children[3].children[3];
}
private Node findNode(Node root, int value) {
if (root == null) {
return null;
}
if (root.data == value) {
return root;
}
for (int i = 0; i < root.children.length; i++) {
Node found = findNode(root.children[i], value);
if (found != null) {
return found;
}
}
return null;
}
public void LCA(int node1, int node2) {
Node n1 = findNode(root, node1);
Node n2 = findNode(root, node2);
Set<Node> ancestors = new HashSet<Node>();
while (n1 != null) {
ancestors.add(n1);
n1 = n1.parent;
}
while (n2 != null) {
if (ancestors.contains(n2)) {
System.out.println("Found common ancestor between " + node1 + " and " + node2 + ": node " + n2.data);
return;
}
n2 = n2.parent;
}
}
public static void main(String[] args) {
LCA tree = new LCA();
tree.LCA(33, 27);
}
}

The LCA algorithm works for any tree (not necessarily binary and not necessarily balanced). Your "simple algorithm" analysis breaks down since tracing a path to a root node is actually O(N) time and space instead of O(log N)

Just want to point out that the problem is about the rooted tree and not binary search tree. So, in you algorithm the
O(n) time complexity for finding each of the 2 nodes, given the values
O(n) space complexity for storing the path into the Set
O(sqrt(n)) time complexity for going up the tree with the second node and searching in the first n-stored elements.
Checking of each node as we go up from the second node with take O(n), so for n nodes it will take O(sqrt(n)).

The Harel and Tarjan LCA algorithm (reference in the link you gave) uses a pre-calculation with O(n) complexity, after which a lookup is O(1) (not O(sqrt(n) as you claim).

Related

Reordering a binary search tree within the tree itself

If I am given a binary tree that is unordered, what would be the best method of ordering it without just creating a new tree? When I say ordered, I mean such that all nodes in a left subtree is less than the root node and all nodes in a right subtree is greater than the root node.
I appreciate that the most optimal way to make an undordered binary tree into a binary seach tree is to extract all the nodes then insert them into a new tree, but is there another approach involving switching the placement of nodes in the original tree that could be done algorithmically?
The method of creating a new tree is certainly the way to go, but just as an excercise, it is possible to sort a binary tree in-place.
You could for instance implement bubble sort, such that all nodes remain in place, but their values are swapped in the process.
For this to work you need to implement an inorder traversal. Then keep repeating a full inorder traversal, where you compare the values of two successively visited nodes, and swap their values if they are not in the right order. When an inorder traversal does not result in at least one swap, the tree is sorted and the process can stop.
Here is an implementation in JavaScript:
It first generates a tree with 10 nodes having randomly unsigned integers below 100. The shape of the tree is random too (based on a random "path" that is provided with each insertion)
Then it sorts the tree as described above. As JavaScript has support for generators and iterators, I have used that syntax, but it could also be done with a callback system.
It displays the tree in a very rudimentary way (90° rotated, i.e. with the root at the left side), as it is before and after the sort operation.
class Node {
constructor(value) {
this.value = value;
this.left = this.right = null;
}
}
class Tree {
constructor() {
this.root = null;
}
// Method to add a value as a new leaf node, using the
// binary bits in the given path to determine
// the leaf's location:
addAtPath(value, path) {
function recur(node, path) {
if (!node) return new Node(value);
if (path % 2) {
node.right = recur(node.right, path >> 1);
} else {
node.left = recur(node.left, path >> 1);
}
return node;
}
this.root = recur(this.root, path);
}
*inorder() {
function* recur(node) {
if (!node) return;
yield* recur(node.left);
yield node;
yield* recur(node.right);
}
yield* recur(this.root);
}
toString() {
function recur(node, indent) {
if (!node) return "";
return recur(node.right, indent + " ")
+ indent + node.value + "\n"
+ recur(node.left, indent + " ");
}
return recur(this.root, "");
}
bubbleSort() {
let dirty = true;
while (dirty) {
dirty = false;
let iterator = this.inorder();
// Get first node from inorder traversal
let prevNode = iterator.next().value;
for (let currNode of iterator) { // Get all other nodes
if (prevNode.value > currNode.value) {
// Swap
const temp = prevNode.value;
prevNode.value = currNode.value;
currNode.value = temp;
dirty = true;
}
prevNode = currNode;
}
}
}
}
// Helper
const randInt = max => Math.floor(Math.random() * max);
// Demo:
const tree = new Tree();
for (let i = 0; i < 10; i++) {
tree.addAtPath(randInt(100), randInt(0x80000000));
}
console.log("Tree before sorting (root is at left, leaves at the right):");
console.log(tree.toString());
tree.bubbleSort();
console.log("Tree after sorting:");
console.log(tree.toString());
The time complexity is O(n²) -- typical for bubble sort.
This sorting does not change the shape of the tree -- all nodes stay where they are. Only the values are moved around.

Trying to understand the space complexity of this algorithm

I see a lot of articles online explaining Time complexity but haven't found anything good that explains space complexity well. I was trying to solve the following interview question
You have two numbers represented by a linked list, where each node
contains a single digit. The digits are stored in reverse order, such
that the Ts digit is at the head of the list. Write a function that
adds the two numbers and returns the sum as a linked list.
EXAMPLE
Input: (7-> 1 -> 6) + (5 -> 9 -> 2).That is, 617 + 295.
Output: 2 -> 1 -> 9.That is, 912.
My solution for it is the following:
private Node addLists(Node head1, Node head2) {
Node summationHead = null;
Node summationIterator = null;
int num1 = extractNumber(head1);
int num2 = extractNumber(head2);
int sum = num1 + num2;
StringBuilder strValue = new StringBuilder();
strValue.append(sum);
String value = strValue.reverse().toString();
char[] valueArray = value.toCharArray();
for (char charValue : valueArray) {
Node node = createNode(Character.getNumericValue(charValue));
if (summationHead == null) {
summationHead = node;
summationIterator = summationHead;
} else {
summationIterator.next = node;
summationIterator = node;
}
}
return summationHead;
}
private Node createNode(int value) {
Node node = new Node(value);
node.element = value;
node.next = null;
return node;
}
private int extractNumber(Node head) {
Node iterator = head;
StringBuilder strNum = new StringBuilder();
while (iterator != null) {
int value = iterator.element;
strNum.append(value);
iterator = iterator.next;
}
String reversedString = strNum.reverse().toString();
return Integer.parseInt(reversedString);
}
Can someone please deduce the space complexity for this? Thanks.
The space complexity means "how does the amount of space required to run this algorithm change asymptotically as the inputs get larger"?
So you have two lists of length N and M. The resultant list will have length max(N,M), possibly +1 if there's a carry. But that +1 is a constant, and we don't consider it part of the Big-O as the larger of N or M will dominate.
Also note this algo is pretty straightforward. There's no intermediate calculation requiring larger-than-linear space.
The space complexity is max(N,M).

Try to check if Tree is Binary Search Tree

First i insert the tree into an array according to Level order (aka Breadth first) traversal.
and now i check the array
For i=1 to Len(Array) do:
IF 2*i smaller than Len(Array) then:
IF Array[i] smaller than Array[2i] OR Array[i] larger than Array[2i+1] then:
Return false
Else if 2*I larger than Len(Array) then
Return True
But my problem is the algorithm work only if the tree is a complete binary tree
As a hint, a binary tree is a binary search tree if and only if an inorder traversal of the tree lists the keys in sorted order. Try switching from a level-by-level traversal to an inorder traversal and making appropriate modifications.
Hope this helps!
Clear, concise and efficient code: Recursion is cool if you actually understand the phenomenon. The idea is to validate each node of the tree as such that it is always between the min and max value. Start with Integer.MIN_VALUE and Integer.MAX_VALUE as the initial input for min and max.
public boolean isBinarySearch(Node root, int min, int max) {
if (root == null)
return true;
return ((min <= root.val && root.val <= max) && (isBinarySearch(
root.left, min, root.val) && isBinarySearch(root.right,
root.val, max)));
}
You can try this out also.
class Node {
public Node left;
public Node right;
public int val;
public Node(int val) {
this.val = val;
}
}
Now do this.
Node root2 = new Node(12);
root2.left = new Node(7);
root2.left.left = new Node(4);
root2.left.right = new Node(11);
root2.right = new Node(16);
root2.right.left = new Node(14);
root2.right.right = new Node(18);
root2.right.right.left = new Node(17);
System.out
.println("IsBinary="
+ wc.isBinarySearch(root2, Integer.MIN_VALUE,
Integer.MAX_VALUE));
}

Check if a binary tree is balanced (Big-O)

Check if a binary tree is balanced.
The source code on the CTCI 5th:
public class QuestionBrute {
public static int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(getHeight(root.left), getHeight(root.right)) + 1;
}
public static boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
int heightDiff = getHeight(root.left) - getHeight(root.right);
if (Math.abs(heightDiff) > 1) {
return false;
}
else {
return isBalanced(root.left) && isBalanced(root.right);
}
}
public static void main(String[] args) {
// Create balanced tree
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TreeNode root = TreeNode.createMinimalBST(array);
System.out.println("Root? " + root.data);
System.out.println("Is balanced? " + isBalanced(root));
// Could be balanced, actually, but it's very unlikely...
TreeNode unbalanced = new TreeNode(10);
for (int i = 0; i < 10; i++) {
unbalanced.insertInOrder(AssortedMethods.randomIntInRange(0, 100));
}
System.out.println("Root? " + unbalanced.data);
System.out.println("Is balanced? " + isBalanced(unbalanced));
}
}
As the algorithm has to check the height of every node, and we don't save the height in each recursion, the running time should be O(N^2).
First of all let's fix a bit your code. Your function to check if the root is balanced will not work simply because a binary tree is balanced if:
maxHeight(root) - minHeight(root) <= 1
I quote Wikipedia: "A balanced binary tree is commonly defined as a binary tree in which the depth of the two subtrees of every node differ by 1 or less"
Your algorithm will give the wrong answer for this tree:
When you call getHeight(Node7) it will return 3, and when you call getHeight(Node5) it will return 3 as well and since (0>1) == false you will return true :(
To fix this all you have to do is to implement the int MinHeight(TreeNode node) the same way you did getHeight() but with Math.min()
Now to your answer. In terms of runtime complexity whenever you call the getHeight() function from the root you are doing a DFS and since you have to visit all the nodes to find the height of the tree this algorithm will be O(N). Now it is true you execute this algorithm twice, when you call maxHeight(root) and minHeight(root) but since they are both O(N) (given that they do exactly what getHeight() does) the overall complexity will have C*N as an upper limit for some constant C and all N bigger than some N knot i.e. O(N) where N is the number of nodes of your tree ;)
Cheers!

Diameter of a rooted k-ary tree

I'm trying to find a linear-time algorithm using recursion to solve the diameter problem for a rooted k-ary tree implemented with adjacency lists. The diameter of a tree is the maximum distance between any couple of leaves. If I choose a root r (that is, a node whose degree is > 1), it can be shown that the diameter is either the maximum distance between two leaves in the same subtree or the maximum distance between two leaves of a path that go through r. My pseudocode for this problem:
Tree-Diameter(T,r)
if degree[r] = 1 then
height[r] = 0
return 0
for each v in Adj[r] do
for i = 1 to degree[r] - 1 do
d_i = Tree-Diameter(T,v)
height[r] = max_{v in Adj[r]} (height[v]
return max(d_i, max_{v in V} (height[v]) + secmax_{v in V} (height[v], 0) + 1)
To get linear time, I compute the diameter AND the height of each subtree at the same time. Then, I choose the maximum quantity between the diameters of each subtrees and the the two biggest heights of the tree + 1 (the secmax function chooses between height[v] and 0 because some subtree can have only a child: in this case, the second biggest height is 0). I ask you if this algorithm works fine and if not, what are the problems? I tried to generalize an algorithm that solve the same problem for a binary tree but I don't know if it's a good generalization.
Any help is appreciated! Thanks in advance!
In all in tree for finding diameter do as below:
Select a random node A, run BFS on this node, to find furthermost node from A. name this node as S.
Now run BFS starting from S, find the furthermost node from S, name it D.
Path between S and D is diameter of your tree. This algorithm is O(n), and just two time traverses tree. Proof is little tricky but not hard. (try yourself or if you think is not true, I'll write it later). And be careful I'm talking about Trees not general graphs. (There is no loop in tree and is connected).
This is a python implementation of what I believe you are interested in. Here, a tree is represented as a list of child trees.
def process(tree):
max_child_height=0
secmax_child_height=0
max_child_diameter=0
for child in tree:
child_height,child_diameter=process(child)
if child_height>max_child_height:
secmax_child_height=max_child_height
max_child_height=child_height
elif child_height>secmax_child_height:
secmax_child_height=child_height
if child_diameter>max_child_diameter:
max_child_diameter=child_diameter
height=max_child_height+1
if len(tree)>1:
diameter=max(max_child_diameter,max_child_height+secmax_child_height)
else:
diameter=max_child_diameter
return height,diameter
def diameter(tree):
height,diameter=process(tree)
return diameter
This is the recursive solution with Java.
import java.util.ArrayList;
import java.util.List;
public class DiameterOrNAryTree {
public int diameter(Node root) {
Result result = new Result();
getDepth(root, result);
return result.max;
}
private int getDepth(Node node, Result result) {
if (node == null) return 0;
int h1 = 0, h2 = 0;
for (Node c : node.children) {
int d = getDepth(c, result);
if (d > h1) {
h2 = h1;
h1 = d;
} else if (d > h2) h2 = d;
}
result.max = Math.max(result.max, h1 + h2);
return h1 + 1;
}
class Result {
int max;
Result() {
max = 0;
}
}
class Node {
public int val;
public List<Node> children;
public Node() {
children = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
children = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _children) {
val = _val;
children = _children;
}
}
}

Resources