Calculate the longest path between two nodes.
The path is in an arch.
Signature of method is:
public static int longestPath(Node n)
In the example binary tree below, it is 4 (going thru 2-3-13-5-2).
This is what I have right now and for the given tree it just returns 0.
public static int longestPath(Node n) {
if (n != null) {
longestPath(n, 0);
}
return 0;
}
private static int longestPath(Node n, int prevNodePath) {
if (n != null && n.getLeftSon() != null && n.getRightSon() != null) {
int currNodePath = countLeftNodes(n.getLeftSon()) + countRightNodes(n.getRightSon());
int leftLongestPath = countLeftNodes(n.getLeftSon().getLeftSon()) + countRightNodes(n.getLeftSon().getRightSon());
int rightLongestPath = countLeftNodes(n.getRightSon().getLeftSon()) + countRightNodes(n.getRightSon().getRightSon());
int longestPath = currNodePath > leftLongestPath ? currNodePath : leftLongestPath;
longestPath = longestPath > rightLongestPath ? longestPath : rightLongestPath;
longestPath(n.getLeftSon(), longestPath);
longestPath(n.getRightSon(), longestPath);
return longestPath > prevNodePath ? longestPath : prevNodePath;
}
return 0;
}
private static int countLeftNodes(Node n) {
if (n != null) {
return 1+ countLeftNodes(n.getLeftSon());
}
return 0;
}
private static int countRightNodes(Node n) {
if (n != null) {
return 1+ countRightNodes(n.getRightSon());
}
return 0;
}
I understand that I'm missing a key concept somewhere... My brain goes crazy when I try tracking the flow of execution...
Am I right by saying that by finding the longest path among the root, its left & right nodes and then recurse on its left & right nodes passing them the longest path from previous method invocation and finally (when?) return the longest path, I'm not certain as to how you go about returning it...
Maybe it is just as simple:
public static int longestPath(Node n) {
if (n != null) {
return longestPath(n, 0); // forgot return?
}
return 0;
}
Its more complicated than one might think at first sight. Consider the following tree:
1
/ \
2 3
/ \
4 5
/ \ \
6 7 8
/ \ \
9 a b
In this case, the root node is not even in the longest path (a-7-4-2-5-8-b).
So, what you must do is the following: For each node n you must compute the following:
compute longest path in left subtree starting with the root of the left subtree (called L)
compute longest path in right subtree starting with the root of the right subtree (called R)
compute the longest path in left subtree (not necessarily starting with the root of the left subtree) (called l)
compute the longest path in right subtree (not necessarily starting with the root of the right subtree) (called r)
Then, decide, which combination maximizes path length:
L+R+2, i.e. going from a subpath in left subtree to current node and from current node through a subpath in right subtree
l, i.e. just take the left subtree and exclude the current node (and thus right subtree) from path
r, i.e. just take the right subtree and exclude the current node (and thus left subtree) from path
So I would do a little hack and for every node not return just a single int, but a triple of integers containing (L+R+2, l, r). The caller then must decide what to do with this result according to the above rules.
A correct algorithm is:
Run DFS from any node to find the farthest leaf node. Label that node T.
Run another DFS to find the farthest node from T.
The path you found in step 2 is the longest path in the tree.
This algorithm will definitely work, and you're not limited to just binary trees either. I'm not sure about your algorithm:
Am I right by saying that by finding the longest path among the root, its left & right nodes and then recurse on its left & right nodes passing them the longest path from previous method invocation and finally (when???) return the longest path, I'm not certain as to how you go about returning it...
because I don't understand what exactly you're describing. Can you work it by hand on an example or try to explain it better? That way you might get better help understanding if it's correct or not.
You seem to be attempting a recursive implementation of basically the same thing just simplified for binary trees. Your code seems rather complicated for this problem however. Check the discussion here for a simpler implementation.
public int longestPath() {
int[] result = longestPath(root);
return result[0] > result[1] ? result[0] : result[1];
}
// int[] {self-contained, root-to-leaf}
private int[] longestPath(BinaryTreeNode n) {
if (n == null) {
return new int[] { 0, 0 };
}
int[] left = longestPath(n.left);
int[] right = longestPath(n.right);
return new int[] { Util.max(left[0], right[0], left[1] + right[1] + 1),
Util.max(left[1], right[1]) + 1 };
}
Simple Implementation:
int maxDepth(Node root) {
if(root == null) {
return 0;
} else {
int ldepth = maxDepth(root.left);
int rdepth = maxDepth(root.right);
return ldepth>rdepth ? ldepth+1 : rdepth+1;
}
}
int longestPath(Node root)
{
if (root == null)
return 0;
int ldepth = maxDepth(root.left);
int rdepth = maxDepth(root.right);
int lLongPath = longestPath(root.left);
int rLongPath = longestPath(root.right);
return max(ldepth + rdepth + 1, max(lLongPath, rLongPath));
}
Here is my recursive solution in C++:
int longest_dis(Node* root) {
int height1, height2;
if( root==NULL)
return 0;
if( root->left == NULL ) && ( root->right == NULL )
return 0;
height1 = height(root->left); // height(Node* node) returns the height of a tree rooted at node
height2 = height(root->right);
if( root->left != NULL ) && ( root->right == NULL )
return max(height1+1, longest_dis(root->left) );
if( root->left == NULL ) && ( root->right != NULL )
return max(height2+1, longest_dis(root->right) );
return max(height1+height2+2, longest_dis(root->left), longestdis(root->right) );
}
Taking into account #phimuemue example and #IVlad solution, I decided to check it out myself, so here is my implementation of #IVlad solution in python:
def longestPath(graph,start, path=[]):
nodes = {}
path=path+[start]
for node in graph[start]:
if node not in path:
deepestNode,maxdepth,maxpath = longestPath(graph,node,path)
nodes[node] = (deepestNode,maxdepth,maxpath)
maxdepth = -1
deepestNode = start
maxpath = []
for k,v in nodes.iteritems():
if v[1] > maxdepth:
deepestNode = v[0]
maxdepth = v[1]
maxpath = v[2]
return deepestNode,maxdepth +1,maxpath+[start]
if __name__ == '__main__':
graph = { '1' : ['2','3'],
'2' : ['1','4','5'],
'3' : ['1'],
'4' : ['2','6','7'],
'5' : ['2','8'],
'6' : ['4'],
'7' : ['4','9','a'],
'8' : ['5','b'],
'9' : ['7'],
'a' : ['7'],
'b' : ['8']
}
"""
1
/ \
2 3
/ \
4 5
/ \ \
6 7 8
/ \ \
9 a b
"""
deepestNode,maxdepth,maxpath = longestPath(graph,'1')
print longestPath(graph, deepestNode)
>>> ('9', 6, ['9', '7', '4', '2', '5', '8', 'b'])
I think You are overcomplicating things.
Think about the longest path that goes through the node n and doesn't go up to the parent of n. What is the relationship between the length of that path and the heights of both subtries connected to n?
After figuring that out, check the tree recursively reasoning like this:
The longest path for a subtree with the root n is the longest path of the following three:
The longest path in the subtree, whose root is n.left_child
The longest path in the subtree, whose root is n.right_child
The longest path, that goes through the node n and doesn't go up to the parent of n
What if, for each node n, your goal was to compute these two numbers:
f(n): The length of the longest path in the tree rooted at n
h(n): The height of the tree that is rooted at n.
For each terminal node (nodes having null left and right nodes), it is obvious that f and h are both 0.
Now, the h of each node n is:
0 if n.left and n.right are both null
1 + h(n.left) if only n.left is non-null
1 + h(n.right) if only n.right is non-null
1 + max(h(n.left), h(n.right)) if both n.left and n.right are non-null
And f(n) is:
0 if n.left and n.right are both null
max(f(n.left), h(n)) if only n.left is non-null
?? if only n.right is non-null
?? if both n.left and n.right are non-null
(You need to figure out what replaces the two "??" placeholders. There are choices that make this strategy work. I have tested it personally.)
Then, longestPath(Node n) is just f(n):
public class SO3124566
{
static class Node
{
Node left, right;
public Node()
{
this(null, null);
}
public Node(Node left, Node right)
{
this.left = left;
this.right = right;
}
}
static int h(Node n)
{
// ...
}
static int f(Node n)
{
// ...
}
public static int longestPath(Node n)
{
return f(n);
}
public static void main(String[] args)
{
{ // #phimuemue's example
Node n6 = new Node(),
n9 = new Node(),
a = new Node(),
n7 = new Node(n9, a),
n4 = new Node(n6, n7),
b = new Node(),
n8 = new Node(null, b),
n5 = new Node(null, n8),
n2 = new Node(n4, n5),
n3 = new Node(),
n1 = new Node(n2, n3);
assert(longestPath(n1) == 6);
}{ // #Daniel Trebbien's example: http://pastebin.org/360444
Node k = new Node(),
j = new Node(k, null),
g = new Node(),
h = new Node(),
f = new Node(g, h),
e = new Node(f, null),
d = new Node(e, null),
c = new Node(d, null),
i = new Node(),
b = new Node(c, i),
a = new Node(j, b);
assert(longestPath(a) == 8);
}
assert(false); // just to make sure that assertions are enabled.
// An `AssertionError` is expected on the previous line only.
}
}
You should be able to write recursive implementations of f and h to make this code work; however, this solution is horribly inefficient. Its purpose is just to understand the calculation.
To improve the efficiency, you could use memoization or convert this to a non-recursive calculation that uses stack(s).
Well, umm if I've understood your question correctly, here is my solution [but in C++(I'm sorry)]:
int h(const Node<T> *root)
{
if (!root)
return 0;
else
return max(1+h(root->left), 1+h(root->right));
}
void longestPath(const Node<T> *root, int &max)
{
if (!root)
return;
int current = h(root->left) + h(root->right) + 1;
if (current > max) {
max = current;
}
longestPath(root->left, max);
longestPath(root->right, max);
}
int longest()
{
int max = 0;
longestPath(root, max);
return max;
}
Related
when I run the code below, but it doesn't work and error like this
`java:47: error: bad operand types for binary operator '!='
if ( x != false && x != p && x != q){
^
first type: Node
second type: boolean
import java.util.*;
class Tree{
static class Node {
int data;
Node left;
Node right;
Node parent;
Node (int data){
this.data = data;
}
}
Node root;
HashMap<Integer, Node> rootMap;
Tree ( int size ){
rootMap = new HashMap<Integer, Node>();
root = makeBST(0, size - 1, null);
}
Node makeBST(int start, int end, Node parent){
if ( start > end) return null;
int mid = (start + end) / 2;
Node node = new Node(mid);
node.left = makeBST(start, mid -1, node);
node.right = makeBST(mid + 1, end, node);
node.parent = parent;
rootMap.put(mid, node);
return node;
}
Node getNode(int data){
return rootMap.get(data);
}
Node commonAncestor( int d1, int d2){
Node p = getNode(d1);
Node q = getNode(d2);
return commonAncestor(root, p, q);
}
Node commonAncestor(Node root, Node p, Node q){
if ( root == null ) return null;
if ( root == p && root == q) return root;
Node x = commonAncestor(root.left, p, q);
if ( x != false && x != p && x != q){
return x;
}
Node y = commonAncestor(root.right, p, q);
if ( y != null && y != p && y != q){
return y;
}
if ( x != null && y != null){
return root;
}else if ( root == p || root == q){
return root;
} else {
return x == null ? y : x;
}
}
}
public class FindAncestor4{
public static void main (String[] args){
Tree t = new Tree(10);
Tree.Node fa = t.commonAncestor(5, 8);
System.out.println("The first common ancestor is " + fa.data);
}
}`
What should I do? I'm really like to appreciate to answer me the reason why occur this error, and how to solve it.
x is a Node object and it cannot be compared with false which is boolean
Since Java is strongly typed, you need to make sure that the two variables you are comparing are actually comparable, in order to use binary operators like + - / * != == > < >= <=. In your secific case, you are comparing an object of type Node to an object of type boolean. Firstly, boolean is a primitive type. Among other things, this means that it does not inherit Object, which is the root from which all non-primitive types in Java stem.
If you take an int to a long, the int will be implicitly converted to a long and then compared.
e.g.
int a = 9;
long b = 9;
if(a == b){
System.out.print("same");
} else {
System.out.print("different");
}
Will return "same".
If it is not possible to implicitly cast the type, then you will get the error you experienced.
You need to figure out how to get the comparison you need. For instance, if you want to see if the object is a node, check with typeof
the compiler tells you everything:
bad operand types for binary operator '!='
first type: Node second type: boolean
I guess you are trying to write x!=null?
I'm working on a polynomial calculator project and I'm working on the first part. Polynomials will be represented in a specific format where two polynomials are a sequence of integers representing the coefficients and exponents. Here's an example.
4 3 -2 2 6 1
6 3 5 2 3 1
Polynomial 1 = 4x^3 - 2x^2 + 6x and Polynomial 2 = 6x^3 + 5x^2 + 3x
I'm supposed to use Linked List to implement this.
Here's my class Node code:
class Node {
int coef;
int power;
Node next;
public Node(int coef, int power) {
this.coef = coef;
this.power = power;
}
}
How do I efficiently separate the coefficients and the exponents and add to the Linked List?
Here's my code for pushing a new Polynomial node:
void addPoly(int coef, int pow) {
Node newNode = new Node(coef, pow);
if (head == null) {
head = newNode;
head.next = null;
} else {
Node curr = head;
while (curr.next != null) {
curr = curr.next;
}
curr.next = newNode;
}
}
Thanks!
You can split your input and then convert each resulting string to an integer. If the power is always in decreasing order you loop backwards to reduce code complexity.
String s = "4 3 -2 2 6 1";
int[] values = Arrays.stream(s.split(" "))
.mapToInt(Integer::valueOf)
.toArray();
assert values.length % 2 == 0;
for (int i = values.length - 1; i > 0; i = i - 2) {
int coeff = values[i - 1];
int power = values[i];
addPoly(coeff, power);
}
addPoly can be reduced to
void addPoly(int coeff, int power) {
Node node = new Node(coeff, power);
node.next = head;
head = node;
}
At the first iteration head is null so the first node becomes the end of the list. Each next step is the predecessor. So the head is it's next. Last but not least the new node becomes the next head.
Java-9 solution, using regex Pattern with two capture groups and a MatchResults stream:
String input = "4 3 -2 2 6 1";
Pattern p = Pattern.compile("\\s*(-?\\d+)\\s+(-?\\d+)");
// add all the polynomials to myList
p.matcher(input).results().forEach(m -> myList.addPoly(m.group(1), m.group(2));
I recently got interviewed and was asked the following question.
Given an n-ary tree, find the maximum path from root to leaf such that maximum path does not contain values from any two adjacent nodes.
(Another edit: The nodes would only have positive values.)
(Edit from comments: An adjacent node means node that share a direct edge. Because its a tree, it means parent-child. So if I include parent, I can not include child and vice versa.)
For example:
5
/ \
8 10
/ \ / \
1 3 7 9
In the above example, the maximum path without two adjacent would be 14 along the path 5->10->9. I include 5 and 9 in the final sum but not 10 because it would violate the no two adjacent nodes condition.
I suggested the following algorithm. While I was fairly sure about it, my interviewer did not seem confident about it. Hence, I wanted to double check if my algorithm was correct or not. It seemed to work on various test cases I could think of:
For each node X, let F(X) be the maximum sum from root to X without two adjacent values in the maximum sum.
The formula for calculating F(X) = Max(F(parent(X)), val(X) + F(grandParent(X)));
Solution would have been
Solution = Max(F(Leaf Nodes))
This was roughly the code I came up with:
class Node
{
int coins;
List<Node> edges;
public Node(int coins, List<Node> edges)
{
this.coins = coins;
this.edges = edges;
}
}
class Tree
{
int maxPath = Integer.MIN_VALUE;
private boolean isLeafNode(Node node)
{
int size = node.edges.size();
for(int i = 0; i < size; i++)
{
if(node.edges.get(i) != null)
return false;
}
return true;
}
// previous[0] = max value obtained from parent
// previous[1] = max value obtained from grandparent
private void helper(Node node, int[] previous)
{
int max = Math.max(previous[0], max.val + previous[1]);
//leaf node
if(isLeafNode(node))
{
maxPath = Math.max(maxPath, max);
return;
}
int[] temp= new int[2];
temp[0] = max;
temp[1] = prev[0];
for(int i = 0; i < node.edges.size(); i++)
{
if(node.edges.get(i) != null)
{
helper(node.edges.get(i), temp);
}
}
}
public int findMax(Node node)
{
int[] prev = new int[2];
prev[0] = 0;
prev[1] = 0;
if(node == null) return 0;
helper(node, prev);
return maxPath;
}
}
Edit: Forgot to mention that my primary purpose in asking this question is to know if my algorithm was correct rather than ask for a new algorithm.
Edit: I have a reason to believe that my algorithm should also have worked.
I was scouring the internet for similar questions and came across this question:
https://leetcode.com/problems/house-robber/?tab=Description
It is pretty similar to the problem above except that it is now an array instead of the tree.
The formal F(X) = Max(F(X-1), a[x] + F(X-2)) works in this case.
Here is my accepted code:
public class Solution {
public int rob(int[] nums) {
int[] dp = new int[nums.length];
if(nums.length < 1) return 0;
dp[0] = nums[0];
if(nums.length < 2) return nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for(int i = 2; i < nums.length; i++)
{
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
}
return dp[nums.length-1];
}
}
The natural solution would be to compute for each node X two values: max path from X to leaf including X and max path from X to leaf, excluding X, let's call them MaxPath(X) and MaxExcluded(X).
For leaf L MaxPath(L) is Value(L) and MaxExcluded(L) is 0.
For internal node X:
MaxPath(X) = Value(X) + Max over child Y of: MaxExcluded(Y)
MaxExcluded(X) = Max over child Y of : Max(MaxExcluded(Y), MaxPath(Y))
The first line means that if you include X, you have to exclude its children. The second means that if you exclude X, you are free to either include or exclude its children.
It's a simple recursive function on nodes which can be computed going leaves-to-parents in O(size of the tree).
Edit: The recursive relation does also work top-down, and in this case you can indeed eliminate storing two values by the observation that MaxExcluded(Y) is actually MaxPath(Parent(Y)), which gives the solution given in the question.
Implementation of what #RafaĆDowgird explained.
/* 5
* 8 10
* 1 3 7 9
* 4 5 6 11 13 14 3 4
*
*
*/
public class app1 {
public static void main(String[] args) {
Node root = new Node(5);
root.left = new Node(8);root.right = new Node(10);
root.left.left = new Node(1);root.left.right = new Node(3);
root.right.left = new Node(7);root.right.right = new Node(9);
root.left.left.left = new Node(4);root.left.left.right = new Node(5);
root.left.right.left = new Node(6);root.left.right.right = new Node(11);
root.right.left.left = new Node(13);root.right.left.right = new Node(14);
root.right.right.right = new Node(4);
System.out.println(findMaxPath(root));
}
private static int findMaxPath(Node root) {
if (root == null) return 0;
int maxInclude = root.data + findMaxPathExcluded(root);
int maxExcludeLeft = Math.max(findMaxPath(root.left), findMaxPathExcluded(root.left));
int maxExcludeRight = Math.max(findMaxPath(root.right), findMaxPathExcluded(root.right));
return Math.max(maxInclude, Math.max(maxExcludeLeft, maxExcludeRight));
}
private static int findMaxPathExcluded(Node root) {
if(root == null) return 0;
int left1 = root.left!=null ? findMaxPath(root.left.left) : 0;
int right1 = root.left!=null ? findMaxPath(root.left.right) : 0;
int left2 = root.right!=null ? findMaxPath(root.right.left) : 0;
int right2 = root.right!=null ? findMaxPath(root.right.right) : 0;
return Math.max(left1, Math.max(right1, Math.max(left2, right2)));
}
}
class Node{
int data;
Node left;
Node right;
Node(int data){
this.data=data;
}
}
I'm trying to figure out the minimum depth to a leaf node using breadth first search. I have the following basic structure
public int BFS(Node root){
if (root == null) return 0;
Queue<Node> q = new LinkedList<Node>();
int min = 1;
q.clear(); // I saw this in a queue example, unsure if needed
q.add(root);
while (! q.isEmpty()){
Node n = q.remove();
if (n.left != null) q.add(n.left);
if (n.right != null) q.add(n.right);
}
}
I'm not sure where to update the min height counter. I had thought about placing it inside the if statements as temp loop variables l & r where I would set them to 1 if the left or right is not null, 0 else. Then add the min of these 2 to the min height but this only works if I'm at one level above the leafs.
The idea should be something like:
First node added to the queue should have distance = 1.
For new nodes added to the queue: distance = actual node distance + 1
When you find a leaf, you return actual node distance. END.
In pseudocode:
root.depth := 1
q := create queue
q.add(root)
while q is not empty
Node n := q.dequeue()
if (n is leaf) then
return n.depth
if (n.left is not null) then
n.left.depth := n.depth + 1
q.add(n.left)
if (n.right is not null) then
n.right.depth := n.depth + 1
q.add(n.right)
return 0
You could use a queue of pairs (node, depth). Since the search is BFT, the first leaf contains the minimum depth.
Based on your code, the algorithm would be something like that (pseudo java code):
public int BFS(Node root)
{
if (root == null)
return 0;
Queue<Pair<Node,int>> q = new LinkedList<Pair<Node,int>>();
q.add(new Pair(root, 0));
while (! q.isEmpty()) {
Pair p = q.remove();
Node n = p.getFirst();
if (n.left == null && n.right == null) // is this a leaf?
return p.getSecond(); // yes! ==> p.getSecond() is its min depth
if (n.left != null)
q.add(new Pair(n.left, p.getSecond() + 1));
if (n.right != null)
q.add(new Pair(n.right, p.getSecond() + 1));
}
}
Of course, you need the Pair class, but I leave to you these details
Generally in BFS, your nodes have a distance field. the roots distance is zero, then whenever you add a new node to the queue, you set its distance to n.distance+1
In fact this is a interview question asked a few days ago.
The interviewer wants me to express the difference between ArrayList and LinkedList, and asked to optimize the insertion operation on ArrayList, in other words, to re-implement add(int index, E element) and of course the complexity of get(int index) operation can be sacrificed.
My answer was to separate the array into k sub-arrays and update a counting array representing the number of elements already in the corresponding sub-array. And the memory of every sub-array is allocated dynamically with an expected initial size. When I need to insert a data into the ArrayList, I can locate a sub-array first, and do the operation within a small array.
And if insertions are not too frequent or the indexes are uniform distributed, the time complexity of inserting can be O(log(k) + n/k + k) in average, where log(k) means we should locate the sub-array first with binary searching on the counting array's sum array, n/k is for data movement or even memory re-allocation, and k stands for the updating of the sum array.
I'm sure there are better solutions. I do need some suggestions, thanks!
One of the solutions could be:
add(int index, E element) always add element to the end of array (you have to also store the index where this element should be added) - complexity O(1)
get(int index) has to restore correct order of array (if some elements were added after the last invocation) - knowing the positions in which each element should be, you can restore correct order in O(n)
You can implement it in a balanced binary tree, so that both add() and get() cost O(logn)
An example implementation will look like (hand-crafted here, will not compile, corner cases not covered):
class Node {
int subTreeSize;
Node left,right;
Element e;
// all i 0-indexed
Node get(int i) {
if (i >= subTreeSize) {
return null;
}
if (left != null) {
if(left.subTreeSize > i) {
return left.get(i);
} else {
i -= left.subTreeSize;
}
}
if (i == 0) {
return this;
}
return right.get(i-1);
}
// add e to the last of the subtree
void append(Element e) {
if(right == null){
right = new Node(e);
} else {
right.append(e);
right = right.balance();
}
subTreeSize += 1;
}
// add e to i-th position
void add(int i, Element e) {
if (left != null) {
if(left.subTreeSize > i) {
add(i,left);
left=left.balance();
} else {
i -= left.subTreeSize;
}
}
if (i == 0) {
if (left == null){
left = new Node(e);
} else {
left.append(e);
left = left.balance();
}
} else {
if (right == null) {
// also in this case i == 1
right = new Node(e);
} else {
right.add(i-1, e);
right = right.balance();
}
}
subTreeSize += 1;
}
// the common balance operation used in balance tree like AVL or RB
// usually just left or right rotation
Node balance() {
...
}
}
public class Tree {
Node root;
public Element get(int i) {
return root.get(i).e;
}
public void add(int i, Element e) {
if (root == null) {
root = new Node(e);
} else {
root.add(i,e);
root = root.balance();
}
}
}
A variant of an order statistic tree would allow you to add and get by index in O(log n).
The basic idea is as follows:
Have each node store the size of the subtree rooted at that node.
The index of a node will correspond to its position in the in-order traversal of the tree.
This means that the ordering of the nodes is determined based on where in the tree they appear - this is not the way a binary search tree typically works, where the nodes' elements have some ordering that's not dependent on where in the tree it appears (e.g. f is greater than a in a regular BST ordered lexicographically, but in our case f may be smaller or greater than a, since it's ordered based on the index of f and a).
To add or get, we start at the root and recursively go through the tree, determining whether our insert or lookup position is to the left or right based on the target index and the subtree sizes.
More specifically, we have the following recursive definitions:
(with some added complexity for null nodes and actually inserting the node)
node.add(index, element):
if index <= left.subtreeSize
left.add(index, element)
else
// anything to the right is after left subtree and current node, so those must be excluded
right.add(index - left.subtreeSize - 1, element)
node.get(index, element):
if index == left.subtreeSize
return node
if index < left.subtreeSize
return left.get(index)
else
return right.get(index - left.subtreeSize - 1)
To understand this better, the following example tree might be helpful:
Values: Indices (in-order pos): Subtree sizes:
a 5 8
/ \ / \ / \
b g 1 6 5 2
/ \ \ / \ \ / \ \
f c h 0 3 7 1 3 1
/ \ / \ / \
e d 2 4 1 1
If we want to insert a new node at position 5, for example, it will be inserted to the right of d.
Below is a small test program to demonstrate this (creating the tree shown above).
Note that balancing will still need to be done to achieve O(log n) running time per operation.
class Test
{
static class Node<T>
{
Node<T> left, right;
T data;
int subtreeCount;
Node(T data) { this.data = data; subtreeCount = 1; }
public String toString(int spaces, char leftRight)
{
return String.format("%" + spaces + "s%c: %s\n", "", leftRight, data.toString())
+ (left != null ? left.toString(spaces+3, 'L') : "")
+ (right != null ? right.toString(spaces+3, 'R') : "");
}
int subtreeSize(Node<T> node)
{
if (node == null)
return 0;
return node.subtreeCount;
}
// combined add and get into 1 function for simplicity
// if data is null, it is an get, otherwise it's an add
private T addGet(int index, T data)
{
if (data != null)
subtreeCount++;
if (index == subtreeSize(left) && data == null)
return this.data;
if (index <= subtreeSize(left))
{
if (left == null && data != null)
return (left = new Node<>(data)).data;
else
return left.addGet(index, data);
}
else if (right == null && data != null)
return (right = new Node<>(data)).data;
else
return right.addGet(index-subtreeSize(left)-1, data);
}
}
static class TreeArray<T>
{
private Node<T> root;
public int size() { return (root == null ? 0 : root.subtreeCount); }
void add(int index, T data)
{
if (index < 0 || index > size())
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
if (root == null)
root = new Node<>(data);
else
root.addGet(index, data);
}
T get(int index)
{
if (index < 0 || index >= size())
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
return root.addGet(index, null);
}
#Override
public String toString() { return root == null ? "Empty" : root.toString(1, 'X'); }
}
public static void main(String[] args)
{
TreeArray<String> tree = new TreeArray<>();
tree.add(0, "a");
tree.add(0, "b");
tree.add(1, "c");
tree.add(2, "d");
tree.add(1, "e");
tree.add(0, "f");
tree.add(6, "g");
tree.add(7, "h");
System.out.println("Tree view:");
System.out.print(tree);
System.out.println("Elements in order:");
for (int i = 0; i < tree.size(); i++)
System.out.println(i + ": " + tree.get(i));
}
}
This outputs:
Tree view:
X: a
L: b
L: f
R: c
L: e
R: d
R: g
R: h
Elements in order:
0: f
1: b
2: e
3: c
4: d
5: a
6: g
7: h
Live demo.
LinkedList is a linked-list with access\insert\remove requires O(n), linked-lists support sequential access O(n).
ArrayList is an array with insert\remove requires O(2n), but access requires O(1), arrays support random access O(1).
to find a more optimal hybrid structure, you can start with this:
template <T>
public class LinkedArrayList
{
LinkedList<ArrayList<T>> list;
public LinkedArrayList ()
{
list = new LinkedList<ArrayList<T>> ();
}
// ..
}
You'll have to balance segments (arrays) in the list between access complexity, and insert\remove complexity