0

We got an assignment where we need to code:

  • A Binary Search Tree
  • The Tree has to be complete, not perfect (which means all nodes which are not on the lowest level or second lowest level should have 2 children, while the nodes on the lowest level should be as far left as possible)
  • We need to insert to the tree in level order
  • So if I have an Array with elements {0, 1, 2, 3, 4, 5, 6, 7} the root should be 4, with 2, 1, 3, 0 on the left side, and 6, 5, 7 on the right side.

  • The level order insert would be: 4, 2, 6, 1, 3, 5, 7, 0

  • Just taking the middle of the Array and put it as root doesn't work. If you got an array of 1 to 9 elements, you would have 4 as root (int value in java, double would be 4.5), and you would have 5 elements on the right side, and 4 on the left side. This is not a complete tree. Not even a perfect tree.

enter image description here

My code is only able to insert at either left or right depending if it's greater og smaller than the root, no level order insertion. The Anytype x parameter is the value to be inserted, while the BinaryNode t is the current node we are at in the tree (that's how we compare if we need to insert the new value to the left or right)

private BinaryNode<AnyType> insert( AnyType x, BinaryNode<AnyType> t )
{
    if( t == null )
        return new BinaryNode<>( x, null, null );

    int compareResult = x.compareTo( t.element );

    if( compareResult < 0 )
        t.left = insert( x, t.left );
    else if( compareResult > 0 )
        t.right = insert( x, t.right );
    else
        ;  // Duplicate; do nothing
    return t;
}

How can I insert in level order and still maintain a binary search tree? Should I use some form of recursion?

My whole program

import java.nio.BufferUnderflowException;
import java.util.*;

import static java.lang.Math.pow;

/**
 * Implements an unbalanced binary search tree.
 * Note that all "matching" is based on the compareTo method.
 * @author Mark Allen Weiss
 */
public class BinarySearchTree<AnyType extends Comparable<? super AnyType>>
{
    /**
     * Construct the tree.
     */
    public BinarySearchTree( )
    {
        root = null;
    }

    /**
     * Insert into the tree; duplicates are ignored.
     * @param x the item to insert.
     */
    public void insert( AnyType x )
    {
        root = insert( x, root );
    }

    /**
     * Test if the tree is logically empty.
     * @return true if empty, false otherwise.
     */
    public boolean isEmpty( )
    {
        return root == null;
    }

    /**
     * Print the tree contents in sorted order.
     */
    public void printTree( )
    {
        if( isEmpty( ) )
            System.out.println( "Empty tree" );
        else
            printTree( root );
    }

    /**
     * Internal method to insert into a subtree.
     * @param x the item to insert.
     * @param t the node that roots the subtree.
     * @return the new root of the subtree.
     */
    private BinaryNode<AnyType> insert( AnyType x, BinaryNode<AnyType> t )
    {
        if( t == null )
            return new BinaryNode<>( x, null, null );

        int compareResult = x.compareTo( t.element );

        if( compareResult < 0 )
            t.left = insert( x, t.left );
        else if( compareResult > 0 )
            t.right = insert( x, t.right );
        else
            ;  // Duplicate; do nothing
        return t;
    }

    /**
     * Internal method to print a subtree in sorted order.
     * @param t the node that roots the subtree.
     */
    private void printTree( BinaryNode<AnyType> t )
    {
        if( t != null )
        {
            printTree( t.left );
            System.out.println( t.element );
            printTree( t.right );
        }
    }

    /**
     * Internal method to compute height of a subtree.
     * @param t the node that roots the subtree.
     */
    private int height( BinaryNode<AnyType> t )
    {
        if( t == null )
            return -1;
        else
            return 1 + Math.max( height( t.left ), height( t.right ) );    
    }

    // Basic node stored in unbalanced binary search trees
    private static class BinaryNode<AnyType>
    {
            // Constructors
        BinaryNode( AnyType theElement )
        {
            this( theElement, null, null );
        }

        BinaryNode( AnyType theElement, BinaryNode<AnyType> lt, BinaryNode<AnyType> rt )
        {
            element  = theElement;
            left     = lt;
            right    = rt;
        }

        AnyType element;            // The data in the node
        BinaryNode<AnyType> left;   // Left child
        BinaryNode<AnyType> right;  // Right child
    }

      /** The tree root. */
    private BinaryNode<AnyType> root;

        // Test program
    public static void main( String [ ] args )
    {
        BinarySearchTree<Integer> t = new BinarySearchTree<>( );

        t.insert(2);
        t.insert(1);
        t.insert(3);
        t.printTree();
    }
}
  • You are missing the point of the project. There is no such thing as a level order insertion. The trick is you should be able to insert the elements in any order but the tree will sort and balance the tree as elements are inserted. – Charles Oct 09 '18 at 15:55
  • No, my lecturer told me I need to insert them in level order. And there is something called level order insertion, take a look at this binary tree (not binary search tree) which uses level order insertion to make a complete tree: https://www.geeksforgeeks.org/construct-complete-binary-tree-given-array/ –  Oct 09 '18 at 15:56
  • This seems to be like a Binary Heap issue. if parent has a index of n then child are at 2*n+1 (left node) and 2*n+2 (right node) you can also reverse this and figure out the parent. So you can just iterate through your list and build the tree in one go. – Charles Oct 09 '18 at 16:08
  • Not for a Binary Search Tree. The left node can never be 2*n+1, that would make it greater than its parent and therefor not on the left side, it should then be on the right side. If we are talking a sorted Array. On a non sorted Array it would only be more non predictable. –  Oct 09 '18 at 16:11
  • not sure what you mean... you are calling insert on (4, 2, 6, 1, 3, 5, 7, 0) right? so starting at root 4 with index 0 . left would be 0*2+1= index 1, right would be 0*2+2=2 index 2. and you just keep going. – Charles Oct 09 '18 at 16:15
  • Yes, but here you are starting with the Array already made manually complete. The array is not given as 4, 2, 6, 1, 3, 5, 7, 0. The array is given as 1,2,3,4,5,6,7. The Tree itself should figure out how to insert from the array in this way: 4,2,6,1,3,5,7,0 –  Oct 09 '18 at 16:20
  • then you are just writing a self balancing binary tree like my first answer and you are not doing a level order insertion. – Charles Oct 09 '18 at 16:24
  • Is the input array always sorted? – FGreg Oct 09 '18 at 16:28
  • They don't have to, but I sort it as I guess it makes it easier? –  Oct 09 '18 at 16:29
  • @Charles a self balancing binary search tree is not a complete tree, it can have children at the right most end while the left is empty. It's not the same. –  Oct 09 '18 at 16:29
  • "The Tree has to be complete, not perfect". Please clarify: does this mean the tree has to be complete, but does not have to be perfect? Or does it mean the tree has to be complete, and must not be perfect? – FGreg Oct 09 '18 at 17:54
  • It can be perfect, but it doesn't HAVE to be. –  Oct 09 '18 at 18:04
  • I think this is what you are looking for https://www.geeksforgeeks.org/leftist-tree-leftist-heap/ – David Coler Oct 09 '18 at 19:25
  • That kind of tree is anything but complete :D –  Oct 09 '18 at 19:32

2 Answers2

0

I think you should do it this manner (code is in C#, but shouldn't be very hard to convert it to Java):

//list should be sorted
public void myFunc(List<int> list){
        Queue<List<int>> queue = new Queue<List<int>>();
         queue.Enqueue(list);

        while(queue.Any()){
            List<int> l = queue.Dequeue();
            int rindex = findRoot(l);
            //print r or store it somewhere
            Console.WriteLine(l.ElementAt(rindex));
            List<int> leftlist = l.GetRange(0, rindex);
            List<int> rightlist = l.GetRange(rindex + 1, l.Count-rindex-1);
            //leftlist = list l from index 0 to r-1; rightlist = list l from index r+1 to end.
            if (leftlist.Any())
            {
                queue.Enqueue(leftlist);
            }

            if (rightlist.Any())
            {
                queue.Enqueue(rightlist);
            }

        }

    }

******EDIT: *********************************************

For finding the root every time you have a list of size n, do the following:

public int findRoot(List<int> list)
        {
            int n = list.Count;

            double h = Math.Ceiling(Math.Log(n + 1, 2)) - 1;

            double totNodesInCompleteBinaryTreeOfHeighthMinusOne = Math.Pow(2, h) - 1;
            double nodesOnLastLevel = n - totNodesInCompleteBinaryTreeOfHeighthMinusOne;

            double nodesOnLastLevelInRightSubtree = 0;

            if (nodesOnLastLevel > Math.Pow(2, h - 1))
            {
                nodesOnLastLevelInRightSubtree = nodesOnLastLevel - Math.Pow(2, h - 1);
            }

            int rindex = (int)(n - nodesOnLastLevelInRightSubtree - 1 - ((totNodesInCompleteBinaryTreeOfHeighthMinusOne - 1) / 2));

            return rindex;
        }
mettleap
  • 1,390
  • 8
  • 17
  • What is the findroot method? Is it just the root element value? And wouldn't adding the left and right list to the queue be like: 1,2,3 to the left and 5,6,7 to the right? Then it would insert 1 to the left first. It should be 2. –  Oct 10 '18 at 05:25
  • Findroot finds the element in the list which should be the root. I am assuming that the list you provide is sorted. Now once you pass 1,2,3 as the left list and 5,6,7 as the right list in the queue, you will be popping 1,2,3 list first and assuming it to be a complete binary search tree, you will find its root element, which is 2 - which is what you want – mettleap Oct 10 '18 at 05:33
  • The Arraylist l which = queue.pop would always be 1 element, why does it have to be a list then? If it's always 1 element how could I use the findroot method on it? –  Oct 10 '18 at 05:44
  • No, the queue.pop will give you a list of elements. Not a single element. You are pushing a whole list object into the queue. That particular queue is a queue is lists, not single elements – mettleap Oct 10 '18 at 05:46
  • The queue.pop returns only 1 element, not a list. This is from the Java library: * @return the element at the front of this list (which is the top * of the stack represented by this list) –  Oct 10 '18 at 06:01
  • Yeah, the statement in the java library doc is correct: see the queue maintains a list in the background, right? I am saying the individual elements stored in that background list are of type lists themselves. Try doing it yourself - initialize a queue variable as I have done and add a list to it. Later, pop the first element and see whether it is a list. – mettleap Oct 10 '18 at 06:19
  • (log(n+1)-1) gives me something weird. log(10+1) - 1 = 0.04 The 10 is the amount of nodes –  Oct 10 '18 at 06:53
  • I have tried to do what you told me, I can't get it to work, I will posts it in my main post now. –  Oct 10 '18 at 07:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181621/discussion-between-mettleap-and-mth1417). – mettleap Oct 10 '18 at 14:43
  • this line: double h = Math.Ceiling(Math.Log(n + 1, 2)) - 1; Is giving an error because the 1,2, it should only be one number, like 1 without 2, as the Log method in Java only takes 1 parameter, so n+1 is one parameter, but n+1,2 is two parameters –  Oct 11 '18 at 06:16
  • If I remove the the 2, and only say n+1 it will work but will give a wrong root –  Oct 11 '18 at 06:17
  • Math.Log(n + 1, 2) in C# == Math.log(n+1)/Math.log(2) in Java – mettleap Oct 11 '18 at 23:06
  • Thanks for the help. –  Oct 12 '18 at 21:14
0

The complete BST part took a bit of time to sort out what that actually is. Your requirement also calls for order level insert. I can't say this does "inserts", but it builds the BST in order level.

The input List must be sorted first.

The building in order level is accomplished by taking the root and adding it to the BST, then splitting what is left into left and right lists, adding them into a list of lists, then processing the list of lists. Each round of splitting and adding to a list of lists is a level of insertion.

The complete part is more difficult, as was noticed. The way to handle that is to compute the root for the list differently than a normal balanced tree. In a normal balanced tree the root index is at length/2. For the BST to be complete the root index has to be offset so that nodes that would normally appear on the right side of the root instead appear on the left side of the root. As long as the computation works for any length list then each split sublist is built properly.

From what I can tell computing the offset done by incrementing the offset for each additional element in length until you get to 1/2 of the width of a level. So, a BST with height of 4 has 8 elements in the lowest level. Lists of size 8, 9, 10, … 15 create BST with height of 4. For those lists the root index into the list is then 4, 5, 6, 7, 7, 7, 7, 7.

Seems to work.

public class Node<T extends Comparable<T>> {
    protected Node<T> left;
    protected Node<T> right;
    protected T data;   
}

public class BTree<T extends Comparable<T>> {
    private Node<T> root = new Node<>();
    public void addData(T data) {
        Node<T> parent = root;
        while (parent.data != null ) {
            if ( data.compareTo( parent.data ) > 0 ) {
                if ( parent.right == null ) 
                    parent.right = new Node<>();
                parent = parent.right;
            } else {
                if ( parent.left == null ) 
                    parent.left = new Node<>();
                parent = parent.left;
            }
        }
        parent.data = data;
    }
}

private void run() {
    for ( int i = 2; i < 65; ++i ) {
        List<Integer> intList = IntStream.range(1, i).boxed().collect(Collectors.toList());
        BTree<Integer> bTree = new BTree<>();
        List<List<Integer>> splitLists = new ArrayList<>();
        splitLists.add(intList);
        while (splitLists.size() > 0 ) {
            List<List<Integer>> tSplitLists = new ArrayList<>();
            for ( List<Integer> tIntList: splitLists) {
                int length = tIntList.size();
                // compute starting point
                int mid = calcMid(length);      // length/2 ; //+ calcOffset(length);
                bTree.addData(tIntList.get(mid));
                if ( mid - 0 > 0)
                    tSplitLists.add(tIntList.subList(0, mid));
                if ( length - (mid+1) > 0)
                    tSplitLists.add(tIntList.subList(mid+1, length));
            }
            splitLists = tSplitLists;
        }
        bTree.printNode();
    }
}
private int calcMid(int length) {
    if ( length <= 4 )
        return length / 2;
    int levelSize = 1;
    int total = 1;
    while ( total < length ) {
        levelSize *= 2;
        total += levelSize;
    }
    int excess = length - (total - levelSize);
    int minMid = (total - levelSize + 1) / 2;
    if ( excess <= levelSize / 2 ) {
        return minMid + (excess - 1); 
    } else {
        int midExcess = levelSize/2; 
        return minMid + (midExcess - 1);
    }

}

See How to print binary tree diagram? for code on printing a binary tree.

PS> I'm sure you can make it a bit better by clearing and copying Lists instead of making new ones each time.

EDIT: Did you go and get the printNode code referenced above?

1 

 2   
/   
1   

 2   
/ \ 
1 3

   3       
  / \   
 /   \  
 2   4   
/       
1

And so on …

K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
  • Hello, thank you very much for your time :) Your program can run, but I seem to get this output: 1 2 1 2 1 3 3 2 4 1 4 2 5 1 3 4 2 6 1 3 5 4 2 6 1 3 5 7 5 3 7 2 4 6 8 1 which I don't really understand. There are lots of same numbers, which there can't be in a BST. Did you get the same :)? –  Oct 11 '18 at 05:56
  • I set the for loop to 10 from 65 –  Oct 11 '18 at 06:28
  • Your calculate mid is perfect and always finds the correct root, but I can't figure out why the output of the tree is as it is. –  Oct 11 '18 at 06:43
  • It is only the calculate root method that works. You haven't answered my earlier question about the output. I can't accept the answer when it doesn't fulfill my question. Or else other people searching will think it works when it's not :) –  Oct 12 '18 at 19:08
  • I accepted it in another post which was only about finding the root. I accepted the answer as the right answer there. But this post is not only about the root. –  Oct 12 '18 at 19:10
  • I you can't figure out what the output is perhaps you should change majors. – K.Nicholas Oct 12 '18 at 19:11
  • I will accept your answer as it was the closest to my request. –  Oct 12 '18 at 21:11
  • The output "1 2 1 2 1 3 3 2 4 1 4 2 5 1 3 4 2 … " comes from the routine doing many different BTrees. First, length of 1, so add order is 1. Second, length of 2, so add order is 2 1. Third, length of 3, so 2 1 3. 4-> 3 2 4 1, 5-> 4 2 5 1 3 4. If you just output that without line breaks you get 1 2 1 2 1 3 3 2 4 1 4 2 5 1 3 and so on. You said you wanted order level adds so the above does order level adds with the whole split list thing. Thanks for accepting. – K.Nicholas Oct 12 '18 at 22:45
  • Ah I can see how it works now. The output makes sense. I will try to implement line breaks to make it easier to see. Thank you :) –  Oct 13 '18 at 07:20