From e77770339e10d4a1326a2c60f29e35a25585fc37 Mon Sep 17 00:00:00 2001 From: "juergen.weber" Date: Mon, 13 Nov 2023 14:18:54 +0100 Subject: [PATCH] AvlTree and AvlTreeMap lost consistency after delete/add operations AVL tree lost nodes after remove / rebalance. See https://issues.apache.org/jira/browse/DIRSERVER-1991. --- .../server/core/avltree/AvlTreeImpl.java | 578 ++++--------- .../server/core/avltree/AvlTreeMapImpl.java | 799 ++++++------------ .../server/core/avltree/LinkedAvlMapNode.java | 67 +- .../server/core/avltree/LinkedAvlNode.java | 52 -- .../server/core/avltree/AvlTreeMapTest.java | 2 +- .../core/avltree/AvlTreeMarshallerTest.java | 2 +- .../server/core/avltree/AvlTreeTest.java | 12 +- 7 files changed, 444 insertions(+), 1068 deletions(-) diff --git a/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeImpl.java b/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeImpl.java index e9f8e90950..63a11245a9 100644 --- a/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeImpl.java +++ b/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeImpl.java @@ -47,7 +47,6 @@ public class AvlTreeImpl implements AvlTree /** size of the tree */ private int size; - /** * Creates a new instance of AVLTree. * @@ -62,6 +61,7 @@ public AvlTreeImpl( Comparator comparator ) /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#getComparator() */ + @Override public Comparator getComparator() { return comparator; @@ -71,11 +71,9 @@ public Comparator getComparator() /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#insert(K) */ + @Override public K insert( K key ) { - LinkedAvlNode node, temp; - LinkedAvlNode parent = null; - int c; if ( root == null ) { @@ -86,53 +84,51 @@ public K insert( K key ) return null; } - node = new LinkedAvlNode<>( key ); - - temp = root; + KeyHolder holder = new KeyHolder<>(); + root = insert( root, key, holder ); + return holder.key; + } - List> treePath = new ArrayList<>(); - while ( temp != null ) + private LinkedAvlNode insert( LinkedAvlNode node, K key, KeyHolder holder ) + { + int cmp = comparator.compare( key, node.key ); + if ( cmp < 0 ) { - treePath.add( 0, temp ); // last node first, for the sake of balance factor computation - parent = temp; - - c = comparator.compare( key, temp.getKey() ); - - if ( c == 0 ) + if ( node.left == null ) { - return key; // key already exists - } - - if ( c < 0 ) - { - temp.isLeft = true; - temp = temp.getLeft(); + LinkedAvlNode left = new LinkedAvlNode<>( key ); + node.left = left; + insertInList( left, node, cmp ); + size++; } else { - temp.isLeft = false; - temp = temp.getRight(); + node.left = insert( node.left, key, holder ); } } - - c = comparator.compare( key, parent.getKey() ); - if ( c < 0 ) + else if ( cmp > 0 ) { - parent.setLeft( node ); + if ( node.right == null ) + { + LinkedAvlNode right = new LinkedAvlNode<>( key ); + node.right = right; + size++; + insertInList( right, node, cmp ); + } + else + { + node.right = insert( node.right, key, holder ); + } } else { - parent.setRight( node ); + holder.key = node.key; + return node; } - insertInList( node, parent, c ); - - treePath.add( 0, node ); - balance( treePath ); - - size++; - return null; + node.height = 1 + Math.max( height( node.left ), height( node.right ) ); + return balance( node ); } @@ -207,169 +203,135 @@ else if ( pos > 0 ) /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#remove(K) */ + @Override public K remove( K key ) { - LinkedAvlNode temp = null; - LinkedAvlNode y = null; - - List> treePath = new ArrayList<>(); - - treePath = find( key, root, treePath ); - if ( treePath == null ) + if ( root == null ) { return null; } - temp = treePath.remove( 0 ); + KeyHolder holder = new KeyHolder<>(); + root = remove( root, key, holder ); + return holder.key; + } - // remove from the doubly linked - removeFromList( temp ); - if ( temp.isLeaf() ) + /** + * Removes the specified key and its associated value from the given subtree. + * + * @param node the subtree + * @param key the key + * @return the updated subtree + */ + private LinkedAvlNode remove( LinkedAvlNode node, K key, KeyHolder holder ) + { + if ( node == null ) { - if ( temp == root ) - { - root = null; - size--; - return key; - } + return null; + } - if ( !treePath.isEmpty() ) - { - detachNodes( temp, treePath.get( 0 ) ); - } + int cmp = comparator.compare( key, node.key ); + if ( cmp < 0 ) + { + node.left = remove( node.left, key, holder ); + } + else if ( cmp > 0 ) + { + node.right = remove( node.right, key, holder ); } else { - if ( temp.left != null ) + holder.key = node.key; + + removeFromList( node ); + size--; + + if ( node.left == null ) { - List> leftTreePath = findMax( temp.left ); - y = leftTreePath.remove( 0 ); - - if ( leftTreePath.isEmpty() ) // y is the left child of root and y is a leaf - { - detachNodes( y, temp ); - } - else - { - detachNodes( y, leftTreePath.remove( 0 ) ); - } - - leftTreePath.addAll( treePath ); - treePath = leftTreePath; - - y.right = temp.right; // assign the right here left will be assigned in replaceNode() - - if ( temp == root ) - { - y.left = temp.left; - root = y; - } - else - { - replaceNode( temp, y, treePath.get( 0 ) ); - } + return node.right; } - else if ( temp.right != null ) + else if ( node.right == null ) + { + return node.left; + } + else { - List> rightTreePath = findMin( temp.right ); - y = rightTreePath.remove( 0 ); - - if ( rightTreePath.isEmpty() ) - { - detachNodes( y, temp ); // y is the right child of root and y is a leaf - } - else - { - detachNodes( y, rightTreePath.remove( 0 ) ); - } - - rightTreePath.addAll( treePath ); - treePath = rightTreePath; - - y.right = temp.right; // assign the right here left will be assigned in replaceNode() - - if ( temp == root ) - { - y.right = temp.right; - root = y; - } - else - { - replaceNode( temp, y, treePath.get( 0 ) ); - } + LinkedAvlNode y = node; + node = mostLeftChild( y.right ); + node.right = deleteMin( y.right ); + node.left = y.left; } } - treePath.add( 0, y ); // y can be null but getBalance returns 0 so np - balance( treePath ); - - size--; - return key; + node.height = 1 + Math.max( height( node.left ), height( node.right ) ); + return balance( node ); } - /** - * Balances the tree by visiting the nodes present in the List of nodes present in the - * treePath parameter.

- * - * This really does the balancing if the height of the tree is greater than 2 and the
- * balance factor is greater than +1 or less than -1.

- * For an excellent info please read the - * Wikipedia article on AVL tree. - * - * @param treePath the traversed list of LinkedAvlNodes after performing an insert/delete operation. - */ - private void balance( List> treePath ) + private LinkedAvlNode mostLeftChild( LinkedAvlNode node ) { - LinkedAvlNode parentNode = null; + LinkedAvlNode current = node; + while ( current.left != null ) + { + current = current.left; + } + return current; + } - int treePathSize = treePath.size(); - for ( LinkedAvlNode node : treePath ) + private LinkedAvlNode deleteMin( LinkedAvlNode node ) + { + if ( node.left == null ) { - int balFactor = getBalance( node ); + return node.right; + } - if ( node != root && treePath.indexOf( node ) < ( treePathSize - 1 ) ) - { - parentNode = treePath.get( treePath.indexOf( node ) + 1 ); - } + node.left = deleteMin( node.left ); + node.height = 1 + Math.max( height( node.left ), height( node.right ) ); + return balance( node ); + } - if ( balFactor > 1 ) - { - if ( getBalance( node.right ) <= -1 ) - { - //------rotate double-left-------- - rotateSingleRight( node.right, node ); - rotateSingleLeft( node, parentNode ); - } - else - // rotate single-left - { - rotateSingleLeft( node, parentNode ); - } - } - else if ( balFactor < -1 ) - { - if ( getBalance( node.left ) >= 1 ) - { - //------rotate double-right-------- - rotateSingleLeft( node.left, node ); - rotateSingleRight( node, parentNode ); - } - else - { - rotateSingleRight( node, parentNode ); - } - } - } + + private LinkedAvlNode rotateRight( LinkedAvlNode x ) + { + LinkedAvlNode y = x.left; + x.left = y.right; + y.right = x; + x.height = 1 + Math.max( height( x.left ), height( x.right ) ); + y.height = 1 + Math.max( height( y.left ), height( y.right ) ); + return y; + } + + + private LinkedAvlNode rotateLeft( LinkedAvlNode x ) + { + LinkedAvlNode y = x.right; + x.right = y.left; + y.left = x; + x.height = 1 + Math.max( height( x.left ), height( x.right ) ); + y.height = 1 + Math.max( height( y.left ), height( y.right ) ); + return y; + } + + + private int height( LinkedAvlNode n ) + { + return n == null ? -1 : n.height; + } + + + public int getBalance( LinkedAvlNode n ) + { + return height( n.left ) - height( n.right ); } /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#isEmpty() */ + @Override public boolean isEmpty() { return root == null; @@ -380,6 +342,7 @@ public boolean isEmpty() * @see org.apache.directory.server.core.avltree.AvlTree#getSize() */ //NOTE: This method is internally used by AVLTreeMarshaller + @Override public int getSize() { return size; @@ -442,6 +405,7 @@ public int getSize() /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#getRoot() */ + @Override public LinkedAvlNode getRoot() { return root; @@ -451,6 +415,7 @@ public LinkedAvlNode getRoot() /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#getKeys() */ + @Override public List getKeys() { List keys = new ArrayList<>(); @@ -469,6 +434,7 @@ public List getKeys() /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#printTree() */ + @Override public void printTree() { if ( isEmpty() ) @@ -477,19 +443,18 @@ public void printTree() return; } - getRoot().setDepth( 0 ); - System.out.println( getRoot() ); - visit( getRoot().getRight(), getRoot() ); + visit( root.right, getRoot(), 0 ); - visit( getRoot().getLeft(), getRoot() ); + visit( root.left, getRoot(), 0 ); } /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#getFirst() */ + @Override public LinkedAvlNode getFirst() { return first; @@ -499,6 +464,7 @@ public LinkedAvlNode getFirst() /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#getLast() */ + @Override public LinkedAvlNode getLast() { return last; @@ -506,167 +472,40 @@ public LinkedAvlNode getLast() /** - * Rotate the node left side once. - * - * @param node the LinkedAvlNode to be rotated - * @param parentNode parent LinkedAvlNode of node - */ - private void rotateSingleLeft( LinkedAvlNode node, LinkedAvlNode parentNode ) - { - LinkedAvlNode temp; - //------rotate single-left-------- - - temp = node.right; - node.right = temp.left; - temp.left = node; - - if ( node == root ) - { - root = temp; - } - else if ( parentNode != null ) - { - if ( parentNode.left == node ) - { - parentNode.left = temp; - } - else if ( parentNode.right == node ) - { - parentNode.right = temp; - } - } - } - - - /** - * Rotate the node right side once. - * - * @param node the LinkedAvlNode to be rotated - * @param parentNode parent LinkedAvlNode of node - */ - private void rotateSingleRight( LinkedAvlNode node, LinkedAvlNode parentNode ) - { - LinkedAvlNode temp; - //------rotate single-right-------- - - temp = node.left; - node.left = temp.right; - temp.right = node; - - if ( node == root ) - { - root = temp; - } - else if ( parentNode != null ) - { - if ( parentNode.left == node ) - { - parentNode.left = temp; - } - else if ( parentNode.right == node ) - { - parentNode.right = temp; - } - } - /* - when the 'parentNode' param is null then the node under rotation is a child of ROOT. - Most likely this condition executes when the root node is deleted and balancing is required. - */ - else if ( root != null && root.left == node ) - { - root.left = temp; - // no need to check for right node - } - } - - - /** - * Detach a LinkedAvlNode from its parent + * Balances the tree by visiting the nodes present in the List of nodes present in the + * treePath parameter.

* - * @param node the LinkedAvlNode to be detached - * @param parentNode the parent LinkedAvlNode of the node + * This really does the balancing if the height of the tree is greater than 2 and the
+ * balance factor is greater than +1 or less than -1.

+ * For an excellent info please read the + * Wikipedia article on AVL tree. */ - private void detachNodes( LinkedAvlNode node, LinkedAvlNode parentNode ) + private LinkedAvlNode balance( LinkedAvlNode node ) { - if ( parentNode != null ) + if ( getBalance( node ) < -1 ) { - if ( node == parentNode.left ) + if ( getBalance( node.right ) > 0 ) { - parentNode.left = node.left; - } - else if ( node == parentNode.right ) - { - parentNode.right = node.left; + node.right = rotateRight( node.right ); } + node = rotateLeft( node ); } - } - - - /** - * - * Replace a LinkedAvlNode to be removed with a new existing LinkedAvlNode - * - * @param deleteNode the LinkedAvlNode to be deleted - * @param replaceNode the LinkedAvlNode to replace the deleteNode - * @param parentNode the parent LinkedAvlNode of deleteNode - */ - private void replaceNode( LinkedAvlNode deleteNode, LinkedAvlNode replaceNode, LinkedAvlNode parentNode ) - { - if ( parentNode != null ) + else if ( getBalance( node ) > 1 ) { - replaceNode.left = deleteNode.left; - - if ( deleteNode == parentNode.left ) - { - parentNode.left = replaceNode; - } - else if ( deleteNode == parentNode.right ) + if ( getBalance( node.left ) < 0 ) { - parentNode.right = replaceNode; + node.left = rotateLeft( node.left ); } + node = rotateRight( node ); } - } - - - /** - * - * Find a LinkedAvlNode with the given key value in the tree starting from the startNode. - * - * @param key the key to find - * @param startNode starting node of a subtree/tree - * @param path the list to be filled with traversed nodes - * @return the list of traversed LinkedAvlNodes. - */ - private List> find( K key, LinkedAvlNode startNode, List> path ) - { - int c; - - if ( startNode == null ) - { - return null; - } - - path.add( 0, startNode ); - c = comparator.compare( key, startNode.key ); - - if ( c == 0 ) - { - return path; - } - else if ( c > 0 ) - { - return find( key, startNode.right, path ); - } - else - { - return find( key, startNode.left, path ); - } + return node; } /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#findGreater(K) */ + @Override public LinkedAvlNode findGreater( K key ) { LinkedAvlNode result = fetchNonNullNode( key, root, root ); @@ -687,6 +526,7 @@ else if ( comparator.compare( key, result.key ) < 0 ) /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#findGreaterOrEqual(K) */ + @Override public LinkedAvlNode findGreaterOrEqual( K key ) { LinkedAvlNode result = fetchNonNullNode( key, root, root ); @@ -707,6 +547,7 @@ else if ( comparator.compare( key, result.key ) <= 0 ) /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#findLess(K) */ + @Override public LinkedAvlNode findLess( K key ) { LinkedAvlNode result = fetchNonNullNode( key, root, root ); @@ -727,6 +568,7 @@ else if ( comparator.compare( key, result.key ) > 0 ) /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#findLessOrEqual(K) */ + @Override public LinkedAvlNode findLessOrEqual( K key ) { LinkedAvlNode result = fetchNonNullNode( key, root, root ); @@ -777,6 +619,7 @@ else if ( c < 0 ) /* (non-Javadoc) * @see org.apache.directory.server.core.avltree.AvlTree#find(K) */ + @Override public LinkedAvlNode find( K key ) { return find( key, root ); @@ -796,12 +639,10 @@ private LinkedAvlNode find( K key, LinkedAvlNode startNode ) if ( c > 0 ) { - startNode.isLeft = false; return find( key, startNode.right ); } else if ( c < 0 ) { - startNode.isLeft = true; return find( key, startNode.left ); } @@ -809,108 +650,14 @@ else if ( c < 0 ) } - /** - * Find the LinkedAvlNode having the max key value in the tree starting from the startNode. - * - * @param startNode starting node of a subtree/tree - * @return the list of traversed LinkedAvlNodes. - */ - private List> findMax( LinkedAvlNode startNode ) - { - LinkedAvlNode x = startNode; - LinkedAvlNode y = null; - List> path; - - if ( x == null ) - { - return null; - } - - while ( x.right != null ) - { - x.isLeft = false; - y = x; - x = x.right; - } - - path = new ArrayList<>( 2 ); - path.add( x ); - - if ( y != null ) - { - path.add( y ); - } - - return path; - } - - - /** - * Find the LinkedAvlNode having the min key value in the tree starting from the startNode. - * - * @param startNode starting node of a subtree/tree - * @return the list of traversed LinkedAvlNodes. - */ - private List> findMin( LinkedAvlNode startNode ) - { - LinkedAvlNode x = startNode; - LinkedAvlNode y = null; - List> path; - - if ( x == null ) - { - return null; - } - - while ( x.left != null ) - { - x.isLeft = true; - y = x; - x = x.left; - } - - path = new ArrayList<>( 2 ); - path.add( x ); - - if ( y != null ) - { - path.add( y ); - } - - return path; - } - - - /** - * Get balance-factor of the given LinkedAvlNode. - * - * @param node a LinkedAvlNode - * @return balance-factor of the node - */ - private int getBalance( LinkedAvlNode node ) - { - if ( node == null ) - { - return 0; - } - - return node.getBalance(); - } - - - private void visit( LinkedAvlNode node, LinkedAvlNode parentNode ) + private void visit( LinkedAvlNode node, LinkedAvlNode parentNode, int depth ) { if ( node == null ) { return; } - if ( !node.isLeaf() ) - { - node.setDepth( parentNode.getDepth() + 1 ); - } - - for ( int i = 0; i < parentNode.getDepth(); i++ ) + for ( int i = 0; i < depth; i++ ) { System.out.print( "| " ); } @@ -929,12 +676,19 @@ else if ( node == parentNode.right ) if ( node.getRight() != null ) { - visit( node.getRight(), node ); + visit( node.getRight(), node, depth + 1 ); } if ( node.getLeft() != null ) { - visit( node.getLeft(), node ); + visit( node.getLeft(), node, depth + 1 ); } } + + private static class KeyHolder + { + + private T key; + + } } diff --git a/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeMapImpl.java b/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeMapImpl.java index ea86d305a7..f3d743dee7 100644 --- a/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeMapImpl.java +++ b/core-avl/src/main/java/org/apache/directory/server/core/avltree/AvlTreeMapImpl.java @@ -20,9 +20,12 @@ package org.apache.directory.server.core.avltree; +import static java.util.stream.Collectors.joining; + import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Objects; /** @@ -55,7 +58,6 @@ public class AvlTreeMapImpl implements AvlTreeMap /** size of the map */ private int size; - /** * Creates a new instance of AVLTreeMap without support for duplicate keys. * @@ -86,6 +88,7 @@ public AvlTreeMapImpl( Comparator keyComparator, Comparator valueComparato /** * {@inheritDoc} */ + @Override public Comparator getKeyComparator() { return keyComparator; @@ -95,61 +98,53 @@ public Comparator getKeyComparator() /** * {@inheritDoc} */ + @Override public Comparator getValueComparator() { return valueComparator; } - - private void dump( LinkedAvlMapNode node ) + + public void dump() { - if ( node.left != null ) + dump( root, "" ); + } + + + private void dump( LinkedAvlMapNode node, String indention ) + { + if ( node.right != null ) { - dump( node.left ); + dump( node.right, indention + " " ); } - + if ( node.value.isSingleton() ) { - System.out.print( "<" + node.key + "," + node.value.getSingleton() + ">" ); + System.out.println( indention + "<" + node.key + "," + node.value.getSingleton() + ">" ); } else { - AvlTree values = node.value.getOrderedSet(); - System.out.print( "<" + node.key + "," ); - boolean isFirst = true; - - for ( V value : values.getKeys() ) - { - if ( isFirst ) - { - isFirst = false; - } - else - { - System.out.print( "," ); - } - - System.out.print( value ); - } - - System.out.print( ">" ); - + String values = node.value.getOrderedSet().getKeys().stream().map( Objects::toString ).collect( joining() ); + System.out.println( indention + "<" + node.key + "," + values + ">" ); } - - if ( node.right != null ) + + if ( node.left != null ) { - dump( node.right ); + dump( node.left, indention + " " ); } } + /** * {@inheritDoc} */ + @Override public V insert( K key, V value ) { - LinkedAvlMapNode node, temp; - LinkedAvlMapNode parent = null; - int c; + if ( key == null || value == null ) + { + throw new IllegalArgumentException( "key or value cannot be null" ); + } if ( root == null ) { @@ -160,67 +155,98 @@ public V insert( K key, V value ) return null; } - node = new LinkedAvlMapNode<>( key, value ); + ValueHolder holder = new ValueHolder<>(); + root = insert( root, key, value, holder ); + return holder.getSingleton(); + } - temp = root; - - List> treePath = new ArrayList<>(); - while ( temp != null ) + private LinkedAvlMapNode insert( LinkedAvlMapNode node, K key, V value, + ValueHolder holder ) + { + int cmp = keyComparator.compare( key, node.key ); + if ( cmp < 0 ) { - treePath.add( 0, temp ); // last node first, for the sake of balance factor computation - parent = temp; - - c = keyComparator.compare( key, temp.getKey() ); - - if ( c == 0 ) - { - V returnValue; - - if ( allowDuplicates ) - { - returnValue = insertDupKey( value, temp ); // key already exists add another value - } - else - { - // replace the existing value with the new value - returnValue = temp.value.setSingleton( value ); - } - - return returnValue; - } - - if ( c < 0 ) + if ( node.left == null ) { - temp.isLeft = true; - temp = temp.getLeft(); + LinkedAvlMapNode left = new LinkedAvlMapNode<>( key, value ); + node.left = left; + insertInList( left, node, cmp ); + size++; } else { - temp.isLeft = false; - temp = temp.getRight(); + node.left = insert( node.left, key, value, holder ); } } - - c = keyComparator.compare( key, parent.getKey() ); - - if ( c < 0 ) + else if ( cmp > 0 ) { - parent.setLeft( node ); + if ( node.right == null ) + { + LinkedAvlMapNode right = new LinkedAvlMapNode<>( key, value ); + node.right = right; + size++; + insertInList( right, node, cmp ); + } + else + { + node.right = insert( node.right, key, value, holder ); + } } else { - parent.setRight( node ); - } + V returnValue; - insertInList( node, parent, c ); + if ( allowDuplicates ) + { + returnValue = insertDupKey( value, node ); // key already exists add another value + } + else + { + // replace the existing value with the new value + returnValue = node.value.setSingleton( value ); + } + + if ( returnValue != null ) + { + holder.value = new SingletonOrOrderedSet<>( returnValue ); + } + return node; + } - treePath.add( 0, node ); - balance( treePath ); + node.height = 1 + Math.max( height( node.left ), height( node.right ) ); + return balance( node ); + } - size++; - return null; + /** + * Balances the tree by visiting the nodes present in the List of nodes present in the + * treePath parameter.

+ * + * This really does the balancing if the height of the tree is greater than 2 and the
+ * balance factor is greater than +1 or less than -1.

+ * For an excellent info please read the + * Wikipedia article on AVL tree. + */ + private LinkedAvlMapNode balance( LinkedAvlMapNode node ) + { + if ( getBalance( node ) < -1 ) + { + if ( getBalance( node.right ) > 0 ) + { + node.right = rotateRight( node.right ); + } + node = rotateLeft( node ); + } + else if ( getBalance( node ) > 1 ) + { + if ( getBalance( node.left ) < 0 ) + { + node.left = rotateLeft( node.left ); + } + node = rotateRight( node ); + } + return node; } @@ -324,6 +350,7 @@ else if ( pos > 0 ) /** * {@inheritDoc} */ + @Override public SingletonOrOrderedSet remove( K key ) { if ( key == null ) @@ -331,36 +358,21 @@ public SingletonOrOrderedSet remove( K key ) throw new IllegalArgumentException( "key cannot be null" ); } - LinkedAvlMapNode temp = null; - - List> treePath = new ArrayList<>(); - - treePath = find( key, root, treePath ); - - if ( treePath == null ) + if ( root == null ) { return null; } - temp = treePath.remove( 0 ); - - if ( temp.isLeaf() && ( temp == root ) ) - { - root = null; - } - else - { - balanceNodesAfterRemove( treePath, temp ); - } - - size--; - return temp.value; + ValueHolder holder = new ValueHolder<>(); + root = remove( root, key, null, holder ); + return holder.value; } /** * {@inheritDoc} */ + @Override public V remove( K key, V value ) { if ( key == null || value == null ) @@ -368,221 +380,179 @@ public V remove( K key, V value ) throw new IllegalArgumentException( "key or value cannot be null" ); } - LinkedAvlMapNode temp = null; + if ( root == null ) + { + return null; + } - List> treePath = new ArrayList<>(); + ValueHolder holder = new ValueHolder<>(); + root = remove( root, key, value, holder ); + return holder.getSingleton(); + } - treePath = find( key, root, treePath ); - if ( treePath == null ) + /** + * Removes the specified key and its associated value from the given subtree. + * + * @param node the subtree + * @param key the key + * @return the updated subtree + */ + private LinkedAvlMapNode remove( LinkedAvlMapNode node, K key, V value, + ValueHolder holder ) + { + if ( node == null ) { return null; } - temp = treePath.remove( 0 ); - - // check if the value matches - if ( allowDuplicates ) + int cmp = keyComparator.compare( key, node.key ); + if ( cmp < 0 ) { - if ( temp.value.isOrderedSet() ) + node.left = remove( node.left, key, value, holder ); + } + else if ( cmp > 0 ) + { + node.right = remove( node.right, key, value, holder ); + } + else + { + if ( value == null ) { - AvlTree dupsTree = temp.value.getOrderedSet(); - V removedVal = dupsTree.remove( value ); - - // if the removal is successful and the tree is not empty - // we don't need to balance the tree, cause just one value - // of the same key was removed - // if the tree is empty because of the removal, the entire - // node will be removed which might require balancing, so we continue - // further down in this function - if ( ( removedVal == null ) || !dupsTree.isEmpty() ) - { - return removedVal;//no need to balance - } + holder.value = node.value; } else { - if ( valueComparator.compare( temp.value.getSingleton(), value ) != 0 ) + if ( allowDuplicates ) + { + if ( node.value.isOrderedSet() ) + { + AvlTree dupsTree = node.value.getOrderedSet(); + V removedVal = dupsTree.remove( value ); + + if ( removedVal == null ) + { + // value was not present + return node; + } + + holder.value = new SingletonOrOrderedSet<>( removedVal ); + + if ( !dupsTree.isEmpty() ) + { + return node; + } + + // the node holds no values anymore and will be deleted + } + else + { + if ( valueComparator.compare( node.value.getSingleton(), value ) != 0 ) + { + return node; // node was not removed, no need to balance + } + + holder.value = node.value; + } + } + else { - return null;// no need to balance + if ( valueComparator.compare( node.value.getSingleton(), value ) != 0 ) + { + return node; // node was not removed, no need to balance + } + + holder.value = node.value; } } - } - if ( temp.isLeaf() && ( temp == root ) ) - { - if ( allowDuplicates ) + removeFromList( node ); + size--; + + if ( node.left == null ) { - if ( temp.value.isSingleton() || temp.value.getOrderedSet().isEmpty() ) - { - root = null; - } + return node.right; + } + else if ( node.right == null ) + { + return node.left; } else - // if dups are not allowed set root to null { - root = null; + LinkedAvlMapNode y = node; + node = mostLeftChild( y.right ); + node.right = deleteMin( y.right ); + node.left = y.left; } - - size--; - return value; } - balanceNodesAfterRemove( treePath, temp ); - - size--; - return value; + node.height = 1 + Math.max( height( node.left ), height( node.right ) ); + return balance( node ); } - /** - * changes the order of nodes after a delete operation and then - * balances the tree - * - * @param treePath the path traversed to find the node temp - * @param delNode the node to be deleted - */ - private void balanceNodesAfterRemove( List> treePath, LinkedAvlMapNode delNode ) + private LinkedAvlMapNode mostLeftChild( LinkedAvlMapNode node ) { - LinkedAvlMapNode y = null; - - // remove from the doubly linked - removeFromList( delNode ); - - if ( delNode.isLeaf() ) + LinkedAvlMapNode current = node; + while ( current.left != null ) { - if ( !treePath.isEmpty() ) - { - detachNodes( delNode, treePath.get( 0 ) ); - } + current = current.left; } - else - { - if ( delNode.left != null ) - { - List> leftTreePath = findMax( delNode.left ); - y = leftTreePath.remove( 0 ); - - if ( leftTreePath.isEmpty() ) // y is the left child of root and y is a leaf - { - detachNodes( y, delNode ); - } - else - { - detachNodes( y, leftTreePath.remove( 0 ) ); - } - - leftTreePath.addAll( treePath ); - treePath = leftTreePath; - - y.right = delNode.right; // assign the right here left will be assigned in replaceNode() - - if ( delNode == root ) - { - y.left = delNode.left; - root = y; - } - else - { - replaceNode( delNode, y, treePath.get( 0 ) ); - } - } - else if ( delNode.right != null ) - { - List> rightTreePath = findMin( delNode.right ); - y = rightTreePath.remove( 0 ); + return current; + } - if ( rightTreePath.isEmpty() ) - { - detachNodes( y, delNode ); // y is the right child of root and y is a leaf - } - else - { - detachNodes( y, rightTreePath.remove( 0 ) ); - } - rightTreePath.addAll( treePath ); - treePath = rightTreePath; + private LinkedAvlMapNode deleteMin( LinkedAvlMapNode node ) + { + if ( node.left == null ) + { + return node.right; + } - y.right = delNode.right; // assign the right here left will be assigned in replaceNode() + node.left = deleteMin( node.left ); + node.height = 1 + Math.max( height( node.left ), height( node.right ) ); + return balance( node ); + } - if ( delNode == root ) - { - y.right = delNode.right; - root = y; - } - else - { - replaceNode( delNode, y, treePath.get( 0 ) ); - } - } - } - treePath.add( 0, y ); // y can be null but getBalance returns 0 so np - balance( treePath ); + private LinkedAvlMapNode rotateRight( LinkedAvlMapNode x ) + { + LinkedAvlMapNode y = x.left; + x.left = y.right; + y.right = x; + x.height = 1 + Math.max( height( x.left ), height( x.right ) ); + y.height = 1 + Math.max( height( y.left ), height( y.right ) ); + return y; } - /** - * Balances the tree by visiting the nodes present in the List of nodes present in the - * treePath parameter.

- * - * This really does the balancing if the height of the tree is greater than 2 and the
- * balance factor is greater than +1 or less than -1.

- * For an excellent info please read the - * Wikipedia article on AVL tree. - * - * @param treePath the traversed list of LinkedAvlMapNodes after performing an insert/delete operation. - */ - private void balance( List> treePath ) + private LinkedAvlMapNode rotateLeft( LinkedAvlMapNode x ) { - LinkedAvlMapNode parentNode = null; + LinkedAvlMapNode y = x.right; + x.right = y.left; + y.left = x; + x.height = 1 + Math.max( height( x.left ), height( x.right ) ); + y.height = 1 + Math.max( height( y.left ), height( y.right ) ); + return y; + } - int treePathSize = treePath.size(); - for ( LinkedAvlMapNode node : treePath ) - { - int balFactor = getBalance( node ); + private int height( LinkedAvlMapNode n ) + { + return n == null ? -1 : n.height; + } - if ( node != root && treePath.indexOf( node ) < ( treePathSize - 1 ) ) - { - parentNode = treePath.get( treePath.indexOf( node ) + 1 ); - } - if ( balFactor > 1 ) - { - if ( getBalance( node.right ) <= -1 ) - { - //------rotate double-left-------- - rotateSingleRight( node.right, node ); - rotateSingleLeft( node, parentNode ); - } - else - // rotate single-left - { - rotateSingleLeft( node, parentNode ); - } - } - else if ( balFactor < -1 ) - { - if ( getBalance( node.left ) >= 1 ) - { - //------rotate double-right-------- - rotateSingleLeft( node.left, node ); - rotateSingleRight( node, parentNode ); - } - else - { - rotateSingleRight( node, parentNode ); - } - } - } + public int getBalance( LinkedAvlMapNode n ) + { + return height( n.left ) - height( n.right ); } /** * {@inheritDoc} */ + @Override public boolean isEmpty() { return root == null; @@ -593,6 +563,7 @@ public boolean isEmpty() * {@inheritDoc} */ //NOTE: This method is internally used by AVLTreeMarshaller + @Override public int getSize() { return size; @@ -641,6 +612,7 @@ public int getSize() /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode getRoot() { return root; @@ -650,6 +622,7 @@ public LinkedAvlMapNode getRoot() /** * {@inheritDoc} */ + @Override public List getKeys() { List keys = new ArrayList<>(); @@ -668,6 +641,7 @@ public List getKeys() /** * {@inheritDoc} */ + @Override public void printTree() { if ( isEmpty() ) @@ -676,19 +650,18 @@ public void printTree() return; } - getRoot().setDepth( 0 ); - - System.out.println( getRoot() ); + System.out.println( root ); - visit( getRoot().getRight(), getRoot() ); + visit( root.right, root, 0 ); - visit( getRoot().getLeft(), getRoot() ); + visit( root.left, root, 0 ); } /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode getFirst() { return first; @@ -698,176 +671,17 @@ public LinkedAvlMapNode getFirst() /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode getLast() { return last; } - /** - * Rotate the node left side once. - * - * @param node the LinkedAvlMapNode to be rotated - * @param parentNode parent LinkedAvlMapNode of node - */ - private void rotateSingleLeft( LinkedAvlMapNode node, LinkedAvlMapNode parentNode ) - { - LinkedAvlMapNode temp; - //------rotate single-left-------- - - temp = node.right; - node.right = temp.left; - temp.left = node; - - if ( node == root ) - { - root = temp; - } - else if ( parentNode != null ) - { - if ( parentNode.left == node ) - { - parentNode.left = temp; - } - else if ( parentNode.right == node ) - { - parentNode.right = temp; - } - } - } - - - /** - * Rotate the node right side once. - * - * @param node the LinkedAvlMapNode to be rotated - * @param parentNode parent LinkedAvlMapNode of node - */ - private void rotateSingleRight( LinkedAvlMapNode node, LinkedAvlMapNode parentNode ) - { - LinkedAvlMapNode temp; - //------rotate single-right-------- - - temp = node.left; - node.left = temp.right; - temp.right = node; - - if ( node == root ) - { - root = temp; - } - else if ( parentNode != null ) - { - if ( parentNode.left == node ) - { - parentNode.left = temp; - } - else if ( parentNode.right == node ) - { - parentNode.right = temp; - } - } - /* - when the 'parentNode' param is null then the node under rotation is a child of ROOT. - Most likely this condition executes when the root node is deleted and balancing is required. - */ - else if ( root != null && root.left == node ) - { - root.left = temp; - // no need to check for right node - } - } - - - /** - * Detach a LinkedAvlMapNode from its parent - * - * @param node the LinkedAvlMapNode to be detached - * @param parentNode the parent LinkedAvlMapNode of the node - */ - private void detachNodes( LinkedAvlMapNode node, LinkedAvlMapNode parentNode ) - { - if ( parentNode != null ) - { - if ( node == parentNode.left ) - { - parentNode.left = node.left; - } - else if ( node == parentNode.right ) - { - parentNode.right = node.left; - } - } - } - - - /** - * - * Replace a LinkedAvlMapNode to be removed with a new existing LinkedAvlMapNode - * - * @param deleteNode the LinkedAvlMapNode to be deleted - * @param replaceNode the LinkedAvlMapNode to replace the deleteNode - * @param parentNode the parent LinkedAvlMapNode of deleteNode - */ - private void replaceNode( LinkedAvlMapNode deleteNode, LinkedAvlMapNode replaceNode, - LinkedAvlMapNode parentNode ) - { - if ( parentNode != null ) - { - replaceNode.left = deleteNode.left; - - if ( deleteNode == parentNode.left ) - { - parentNode.left = replaceNode; - } - else if ( deleteNode == parentNode.right ) - { - parentNode.right = replaceNode; - } - } - } - - - /** - * - * Find a LinkedAvlMapNode with the given key value in the tree starting from the startNode. - * - * @param key the key to find - * @param startNode starting node of a subtree/tree - * @param path the list to be filled with traversed nodes - * @return the list of traversed LinkedAvlMapNodes. - */ - private List> find( K key, LinkedAvlMapNode startNode, - List> path ) - { - int c; - - if ( startNode == null ) - { - return null; - } - - path.add( 0, startNode ); - c = keyComparator.compare( key, startNode.key ); - - if ( c == 0 ) - { - return path; - } - else if ( c > 0 ) - { - return find( key, startNode.right, path ); - } - else - { - return find( key, startNode.left, path ); - } - } - - /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode findGreater( K key ) { LinkedAvlMapNode result = fetchNonNullNode( key, root, root ); @@ -888,6 +702,7 @@ else if ( keyComparator.compare( key, result.key ) < 0 ) /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode findGreaterOrEqual( K key ) { LinkedAvlMapNode result = fetchNonNullNode( key, root, root ); @@ -908,6 +723,7 @@ else if ( keyComparator.compare( key, result.key ) <= 0 ) /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode findLess( K key ) { LinkedAvlMapNode result = fetchNonNullNode( key, root, root ); @@ -928,6 +744,7 @@ else if ( keyComparator.compare( key, result.key ) > 0 ) /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode findLessOrEqual( K key ) { LinkedAvlMapNode result = fetchNonNullNode( key, root, root ); @@ -979,6 +796,7 @@ else if ( c < 0 ) /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode find( K key ) { return find( key, root ); @@ -988,6 +806,7 @@ public LinkedAvlMapNode find( K key ) /** * {@inheritDoc} */ + @Override public LinkedAvlMapNode find( K key, V value ) { if ( key == null || value == null ) @@ -1036,12 +855,10 @@ private LinkedAvlMapNode find( K key, LinkedAvlMapNode startNode ) if ( c > 0 ) { - startNode.isLeft = false; return find( key, startNode.right ); } else if ( c < 0 ) { - startNode.isLeft = true; return find( key, startNode.left ); } @@ -1049,108 +866,14 @@ else if ( c < 0 ) } - /** - * Find the LinkedAvlMapNode having the max key value in the tree starting from the startNode. - * - * @param startNode starting node of a subtree/tree - * @return the list of traversed LinkedAvlMapNodes. - */ - private List> findMax( LinkedAvlMapNode startNode ) - { - LinkedAvlMapNode x = startNode; - LinkedAvlMapNode y = null; - List> path; - - if ( x == null ) - { - return null; - } - - while ( x.right != null ) - { - x.isLeft = false; - y = x; - x = x.right; - } - - path = new ArrayList<>( 2 ); - path.add( x ); - - if ( y != null ) - { - path.add( y ); - } - - return path; - } - - - /** - * Find the LinkedAvlMapNode having the min key value in the tree starting from the startNode. - * - * @param startNode starting node of a subtree/tree - * @return the list of traversed LinkedAvlMapNodes. - */ - private List> findMin( LinkedAvlMapNode startNode ) - { - LinkedAvlMapNode x = startNode; - LinkedAvlMapNode y = null; - List> path; - - if ( x == null ) - { - return null; - } - - while ( x.left != null ) - { - x.isLeft = true; - y = x; - x = x.left; - } - - path = new ArrayList<>( 2 ); - path.add( x ); - - if ( y != null ) - { - path.add( y ); - } - - return path; - } - - - /** - * Get balance-factor of the given LinkedAvlMapNode. - * - * @param node a LinkedAvlMapNode - * @return balance-factor of the node - */ - private int getBalance( LinkedAvlMapNode node ) - { - if ( node == null ) - { - return 0; - } - - return node.getBalance(); - } - - - private void visit( LinkedAvlMapNode node, LinkedAvlMapNode parentNode ) + private void visit( LinkedAvlMapNode node, LinkedAvlMapNode parentNode, int depth ) { if ( node == null ) { return; } - if ( !node.isLeaf() ) - { - node.setDepth( parentNode.getDepth() + 1 ); - } - - for ( int i = 0; i < parentNode.getDepth(); i++ ) + for ( int i = 0; i < depth; i++ ) { System.out.print( "| " ); } @@ -1169,12 +892,12 @@ else if ( node == parentNode.right ) if ( node.getRight() != null ) { - visit( node.getRight(), node ); + visit( node.getRight(), node, depth + 1 ); } if ( node.getLeft() != null ) { - visit( node.getLeft(), node ); + visit( node.getLeft(), node, depth + 1 ); } } @@ -1182,6 +905,7 @@ else if ( node == parentNode.right ) /** * {@inheritDoc} */ + @Override public boolean isDupsAllowed() { return allowDuplicates; @@ -1206,4 +930,19 @@ public void removeAll() root = null; size = 0; } + + private static class ValueHolder + { + + private SingletonOrOrderedSet value; + + T getSingleton() + { + if ( value != null ) + { + return value.getSingleton(); + } + return null; + } + } } diff --git a/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlMapNode.java b/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlMapNode.java index b3dc9a49e2..1c253dc83e 100644 --- a/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlMapNode.java +++ b/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlMapNode.java @@ -45,11 +45,7 @@ public class LinkedAvlMapNode /** The previous node, inferior to the current node */ LinkedAvlMapNode previous; - int depth; - int index; - - boolean isLeft; - int height = 1; + int height = 0; /** @@ -121,18 +117,6 @@ public boolean isLeaf() } - public int getDepth() - { - return depth; - } - - - public void setDepth( int depth ) - { - this.depth = depth; - } - - public int getHeight() { return height; @@ -151,55 +135,6 @@ public void setPrevious( LinkedAvlMapNode previous ) } - public int computeHeight() - { - - if ( right == null && left == null ) - { - height = 1; - return height; - } - - int lh, rh; - - if ( isLeft ) - { - lh = ( left == null ? -1 : left.computeHeight() ); - rh = ( right == null ? -1 : right.getHeight() ); - } - else - { - rh = ( right == null ? -1 : right.computeHeight() ); - lh = ( left == null ? -1 : left.getHeight() ); - } - - height = 1 + Math.max( lh, rh ); - - return height; - } - - - public int getBalance() - { - int lh = ( left == null ? 0 : left.computeHeight() ); - int rh = ( right == null ? 0 : right.computeHeight() ); - - return ( rh - lh ); - } - - - public int getIndex() - { - return index; - } - - - public void setIndex( int index ) - { - this.index = index; - } - - @Override public String toString() { diff --git a/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlNode.java b/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlNode.java index 5c2d7eb7c0..02d195ddce 100644 --- a/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlNode.java +++ b/core-avl/src/main/java/org/apache/directory/server/core/avltree/LinkedAvlNode.java @@ -42,13 +42,10 @@ public class LinkedAvlNode /** The previous node, inferior to the current node */ LinkedAvlNode previous; - int depth; int index; - boolean isLeft; int height = 1; - /** * Creates a new instance of LinkedAvlNode, containing a given value. * @@ -110,18 +107,6 @@ public boolean isLeaf() } - public int getDepth() - { - return depth; - } - - - public void setDepth( int depth ) - { - this.depth = depth; - } - - public int getHeight() { return height; @@ -140,43 +125,6 @@ public void setPrevious( LinkedAvlNode previous ) } - public int computeHeight() - { - - if ( right == null && left == null ) - { - height = 1; - return height; - } - - int lh, rh; - - if ( isLeft ) - { - lh = ( left == null ? -1 : left.computeHeight() ); - rh = ( right == null ? -1 : right.getHeight() ); - } - else - { - rh = ( right == null ? -1 : right.computeHeight() ); - lh = ( left == null ? -1 : left.getHeight() ); - } - - height = 1 + Math.max( lh, rh ); - - return height; - } - - - public int getBalance() - { - int lh = ( left == null ? 0 : left.computeHeight() ); - int rh = ( right == null ? 0 : right.computeHeight() ); - - return ( rh - lh ); - } - - public int getIndex() { return index; diff --git a/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMapTest.java b/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMapTest.java index ff1f4f9256..ca1814cf60 100644 --- a/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMapTest.java +++ b/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMapTest.java @@ -159,7 +159,7 @@ public void testInsert() tree.remove( 24, 3 ); // this causes a single left rotation on node with key 12 - assertTrue( tree.getRoot().getLeft().key == 26 ); + assertTrue( tree.getRoot().getLeft().key == 25 ); } diff --git a/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMarshallerTest.java b/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMarshallerTest.java index f4338cd116..f45b79bc01 100644 --- a/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMarshallerTest.java +++ b/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeMarshallerTest.java @@ -452,7 +452,7 @@ public void testMarshal_UnMarshal() throws FileNotFoundException, IOException unmarshalledTree.insert( 6 ); // will change the root as part of balancing assertTrue( savedTree.getRoot().getKey() == unmarshalledTree.getRoot().getKey() ); - assertTrue( 8 == unmarshalledTree.getRoot().getKey() ); // new root + assertTrue( 9 == unmarshalledTree.getRoot().getKey() ); // new root assertTrue( 37 == unmarshalledTree.getLast().getKey() ); unmarshalledTree.insert( 99 ); diff --git a/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeTest.java b/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeTest.java index 8189d2f698..bccc30aa63 100644 --- a/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeTest.java +++ b/core-avl/src/test/java/org/apache/directory/server/core/avltree/AvlTreeTest.java @@ -147,7 +147,7 @@ public void testInsert() { tree.printTree(); } - assertTrue( tree.getRoot().getLeft().key == 26 ); + assertTrue( tree.getRoot().getLeft().key == 25 ); } @@ -450,7 +450,7 @@ public void testRemoveInRightSubtree() tree.remove( 13 ); - assertEquals( 11, ( int ) tree.find( 8 ).right.key ); + assertEquals( 14, ( int ) tree.find( 8 ).right.key ); } @@ -468,10 +468,10 @@ public void testRemoveInLeftSubtree() tree.remove( 16 ); - assertEquals( 8, ( int ) tree.getRoot().key ); - assertEquals( 12, ( int ) tree.getRoot().right.key ); - assertEquals( 14, ( int ) tree.getRoot().right.right.key ); - assertEquals( 13, ( int ) tree.find( 14 ).left.key ); + assertEquals( 11, ( int ) tree.getRoot().key ); + assertEquals( 13, ( int ) tree.getRoot().right.key ); + assertEquals( 17, ( int ) tree.getRoot().right.right.key ); + assertNull( tree.find( 14 ).left ); }