0
votes

I'm currently in the middle of an AVL Tree insert implementation, and I am struggling with maintaining the balance factors while inserting and backtracking up the tree.

Virtually every AVL implementation I can find as an example uses the height of a node's two sub-tree's to calculate the balance factor, something along the lines of

node.balance = node.right.height - node.left.height

And this is perfectly fine if your Node class looks something like

class Node {
    int value, height;
    Node left, right;
}

Though the problem is that for this particular implementation, it is 'against the rules' to keep track of the height of the node, and instead we can only keep track of the balance factor. So the Node class instead looks like

class Node {
    int value, balance;
    Node left, right;
}

I know that maintaining the balance factor of a node is conceptually similar to maintaining the height for each insert into the tree, but for the life of me I can't figure out all of the situations in which the balance factor should change for a particular node.

At the moment I've got setting the balance factor implemented by instead recursively calling the height function for each and every node ( not optimal! ) to make sure my rotations and general insertion is correct.

node.balance = height(node.right) - height(node.left)

Where height() recursively traverses the tree to find the longest path to a leaf.

And I have verified that the rotation logic is indeed correct, but when I start writing code to maintain the balances by +-1 increments backtracking up the tree, the code immediately turns into spaghetti, as I am clearly not understanding something fundamental about the node balance factor.

If you want to see that code, I've posted it below ( its a bit long ). And the below implementation is also a String AVL Tree, but the idea is the same.

Any input is appreciated, Thanks!

class StringAVLNode {
    private String item;
    private int balance;
    private StringAVLNode left, right;

    // just one constructor, please
    public StringAVLNode(String str) {
        item = str;
        balance = 0;
        left = null; right = null;
    }

    public int getBalance () {
        return balance;
    }

    public void setBalance ( int bal){
        balance = bal;
    }

    public String getItem () {
        return item;
    }

    public StringAVLNode getLeft () {
        return left;
    }

    public void setLeft (StringAVLNode pt){
        left = pt;
    }

    public StringAVLNode getRight () {
        return right;
    }

    public void setRight (StringAVLNode pt){
        right = pt;
    }



    public void insert(String str) {
        root = insert(str, root);
    }


    private StringAVLNode insert(String str, StringAVLNode t) {

        // Base case - Just insert the node
        if (t == null)
            t = new StringAVLNode(str);
        else {
            int balance, leftChildBalance, rightChildBalance;
            leftChildBalance = t.getLeft() != null ? t.getLeft().getBalance() : -99;
            rightChildBalance = t.getRight() != null ? t.getRight().getBalance() : -99;

            // Perform string comparisons to determine left/right insert
            int compareResult = str.compareToIgnoreCase(t.getItem());
            if (compareResult < 0) {
                t.setLeft(insert(str, t.getLeft()));

                if (t.getRight() == null)
                    t.setBalance(t.getBalance()-1);
                else if (leftChildBalance == 0 && t.getLeft().getBalance() != 0)
                    t.setBalance(t.getBalance()-1);
                else if (leftChildBalance == -99 && t.getLeft() != null)
                    t.setBalance(t.getBalance()-1);

            }
            else if (compareResult > 0) {
                t.setRight(insert(str, t.getRight()));


                if (t.getLeft() == null)

                    t.setBalance(t.getBalance()+1);
                else if (rightChildBalance == 0 && t.getRight().getBalance() != 0)
                    t.setBalance(t.getBalance()+1);
                else if (rightChildBalance == -99 && t.getRight() != null)
                    t.setBalance(t.getBalance()+1);
            }
            balance = t.getBalance();


            // Verbosify booleans
            boolean rightImbalance = balance > 1; boolean leftImbalance = balance < -1;

            // Imbalance tree situation calls balanceTrees() to handle the rotation logic
            // ( Keeps insert() succinct )
            if (rightImbalance || leftImbalance)
                t = balanceTrees(balance, t);

        }
        return t;
    }



    // Rotation Handler
    private StringAVLNode balanceTrees(int balance, StringAVLNode t) {

        // Verbosify boolean values
        boolean rightHeavy = balance > 1; boolean leftHeavy = balance < -1;
        boolean requiresDoubleLeft = t.getRight() != null && t.getRight().getBalance() <= -1;
        boolean requiresDoubleRight = t.getLeft() != null && t.getLeft().getBalance() >= 1;

        if (rightHeavy) {

            /** Do double left rotation by right rotating the right child subtree, then
             * rotate left
             */
            if (requiresDoubleLeft) {

                t.setRight(rotateRight(t.getRight()));
                t.getRight().setBalance(0);
                t = rotateLeft(t);
                t.setBalance(0);

            }
            else {
                t = rotateLeft(t);
                t.setBalance(0);
                if (t.getLeft() != null) t.getLeft().setBalance(0);
                if (t.getRight() != null) t.getRight().setBalance(0);
            }
        }

        /** Do double right rotation by left rotating the left child subtree, then
         * rotate right
         */
        else if (leftHeavy) {
            if (requiresDoubleRight) {

                t.setLeft(rotateLeft(t.getLeft()));
                t.getLeft().setBalance(0);
                t = rotateRight(t);
                t.setBalance(0);

            }
            else {
                t = rotateRight(t);
                t.setBalance(0);
                if (t.getLeft() != null) t.getLeft().setBalance(0);
                if (t.getRight() != null) t.getRight().setBalance(0);
            }
        }
        if (t.getLeft() != null) {
            if (t.getLeft().getRight() != null && t.getLeft().getLeft() == null)
                t.getLeft().setBalance(1);
            else if (t.getLeft().getLeft() != null && t.getLeft().getRight() == null)
                t.getLeft().setBalance(-1);
            else if ((t.getLeft().getLeft() != null && t.getLeft().getRight() != null)
                    || (t.getLeft().getLeft() == null && t.getLeft().getRight() == null))
                t.getLeft().setBalance(0);
        }
        if (t.getRight() != null) {
            if (t.getRight().getRight() != null && t.getRight().getLeft() == null)
                t.getRight().setBalance(1);
            else if (t.getRight().getLeft() != null && t.getRight().getRight() == null)
                t.getRight().setBalance(-1);
            else if ((t.getRight().getLeft() != null && t.getRight().getRight() != null)
                    || (t.getRight().getLeft() == null && t.getRight().getRight() == null))
                t.getRight().setBalance(0);
        }
        return t;
    }
}
1

1 Answers

2
votes

Check out my AVL Tree in Java writeup at:

https://debugnotes.wordpress.com/2015/01/07/implementing-an-avl-tree-in-java-part-2

It appears that your implementation does not include any kind of stack based element (recursive or array based) to keep track of how deep you are in the tree. This is a key part of being able to navigate self-balancing tree data structures - being able to search downwards, find and do something with a target node, and then trace backwards to the root node of the tree where it started navigating from, manipulating it as you work your back way up. Using recursion is one way (i.e. using the program stack) or you need to implement your own stack (e.g. use a Queue or LinkedList) but unless your code has a memory structure recording where it's been, unfortunately it'll always get lost.