[Geom4j-developer] SF.net SVN: geom4j:[19] trunk/src/net/sourceforge/geom4j/util
Status: Pre-Alpha
Brought to you by:
skhenkin
|
From: <skh...@us...> - 2009-12-14 22:57:56
|
Revision: 19
http://geom4j.svn.sourceforge.net/geom4j/?rev=19&view=rev
Author: skhenkin
Date: 2009-12-14 22:57:47 +0000 (Mon, 14 Dec 2009)
Log Message:
-----------
AVL tree implementation completed
Modified Paths:
--------------
trunk/src/net/sourceforge/geom4j/util/AVLTree.java
trunk/src/net/sourceforge/geom4j/util/AVLTreeTest.java
trunk/src/net/sourceforge/geom4j/util/BinarySearchTree.java
trunk/src/net/sourceforge/geom4j/util/BinarySearchTreeTestBase.java
trunk/src/net/sourceforge/geom4j/util/SearchTree.java
Modified: trunk/src/net/sourceforge/geom4j/util/AVLTree.java
===================================================================
--- trunk/src/net/sourceforge/geom4j/util/AVLTree.java 2009-12-10 21:16:10 UTC (rev 18)
+++ trunk/src/net/sourceforge/geom4j/util/AVLTree.java 2009-12-14 22:57:47 UTC (rev 19)
@@ -41,6 +41,21 @@
* Contains balance factor for each node.
*/
protected class AVLNode extends Node {
+
+ public int height = 1;
+
+ public AVLNode(E value, AVLNode parent) {
+ super(value, parent);
+ }
+
+ public final int getLeftHeight() {
+ return left == null ? 0 : getLeft().height;
+ }
+
+ public final int getRightHeight() {
+ return right == null ? 0 : getRight().height;
+ }
+
/**
* Each node can have one of the following balance factors
*
@@ -58,12 +73,34 @@
* was lost from the right sub-tree) it will require re-balancing.
* This is achieved by performing a rotation about this node.
*/
- public int balanceFactor = 0;
-
- public AVLNode(E value, Node parent) {
- super(value, parent);
+ public final int getBalanceFactor() {
+ return getRightHeight() - getLeftHeight();
}
-
+
+ @Override
+ public AVLNode getLeft() {
+ return (AVLNode) super.getLeft();
+ }
+
+ @Override
+ public AVLNode getParent() {
+ return (AVLNode) super.getParent();
+ }
+
+ @Override
+ public AVLNode getRight() {
+ return (AVLNode) super.getRight();
+ }
+
+ public boolean isUnbalanced() {
+ int balanceFactor = getBalanceFactor();
+ return balanceFactor < -1 || balanceFactor > 1;
+ }
+
+ /** Recalculate height for the node */
+ public void recalculateHeight() {
+ height = Math.max(getLeftHeight(), getRightHeight()) + 1;
+ }
}
/** Create an empty tree of comparable elements */
@@ -77,15 +114,139 @@
}
@Override
- protected Node insertNode(E e, Node parent) {
- // TODO add code for balancing
- return super.insertNode(e, parent);
+ protected AVLNode insertChildNode(E e, Node parent, int c) {
+ AVLNode insertedNode = (AVLNode) super.insertChildNode(e, parent, c);
+ updateBalance(insertedNode);
+ return insertedNode;
}
@Override
- protected void removeNode(Node node) {
- // TODO add code for balancing
- super.removeNode(node);
+ protected AVLNode createNode(E e, Node parent) {
+ return new AVLNode(e, (AVLNode) parent);
}
+ /** Update balance node and all nodes above it */
+ private void updateBalance(AVLNode node) {
+ while (node != null) {
+ node.recalculateHeight();
+ AVLNode parentNode = node.getParent();
+ if (node.isUnbalanced()) {
+ balanceNode(node);
+ }
+ node = parentNode;
+ }
+ }
+
+ private void balanceNode(AVLNode node) {
+ int balanceFactor = node.getBalanceFactor();
+ assert balanceFactor == -2 || balanceFactor == 2 : balanceFactor;
+ if (balanceFactor < 0) {
+ assert balanceFactor == -2 : balanceFactor;
+ AVLNode leftNode = node.getLeft();
+ if (leftNode.getBalanceFactor() <= 0) { // LL rotation
+ rotateLL(node, leftNode);
+ } else { // LR rotation
+ assert leftNode.getBalanceFactor() == 1 : leftNode.getBalanceFactor();
+ rotateLR(node, leftNode, leftNode.getRight());
+ }
+ } else {
+ assert balanceFactor == 2 : balanceFactor;
+ AVLNode rightNode = node.getRight();
+ if (rightNode.getBalanceFactor() >= 0) { // RR rotation
+ rotateRR(node, rightNode);
+ } else { // RL rotation
+ assert rightNode.getBalanceFactor() == -1 : rightNode.getBalanceFactor();
+ rotateRL(node, rightNode, rightNode.getLeft());
+ }
+ }
+ }
+
+ /** LL-rotation */
+ private void rotateLL(AVLNode node, AVLNode leftNode) {
+ node.left = leftNode.right;
+ if (node.left != null) {
+ node.left.parent = node;
+ }
+ node.recalculateHeight();
+ updateParent(leftNode, node);
+ leftNode.right = node;
+ node.parent = leftNode;
+ leftNode.recalculateHeight();
+ }
+
+ /** RR-rotation */
+ private void rotateRR(AVLNode node, AVLNode rightNode) {
+ node.right = rightNode.left;
+ if (node.right != null) {
+ node.right.parent = node;
+ }
+ node.recalculateHeight();
+ updateParent(rightNode, node);
+ rightNode.left = node;
+ node.parent = rightNode;
+ rightNode.recalculateHeight();
+ }
+
+ /** LR-rotation */
+ private void rotateLR(AVLNode node, AVLNode leftNode, AVLNode rightLeftNode) {
+ node.left = rightLeftNode.right;
+ if (node.left != null) {
+ node.left.parent = node;
+ }
+ node.recalculateHeight();
+ leftNode.right = rightLeftNode.left;
+ if (leftNode.right != null) {
+ leftNode.right.parent = leftNode;
+ }
+ leftNode.recalculateHeight();
+ updateParent(rightLeftNode, node);
+ rightLeftNode.left = leftNode;
+ leftNode.parent = rightLeftNode;
+ rightLeftNode.right = node;
+ node.parent = rightLeftNode;
+ rightLeftNode.recalculateHeight();
+ }
+
+ /** RL-rotation */
+ private void rotateRL(AVLNode node, AVLNode rightNode, AVLNode leftRightNode) {
+ node.right = leftRightNode.left;
+ if (node.right != null) {
+ node.right.parent = node;
+ }
+ node.recalculateHeight();
+ rightNode.left = leftRightNode.right;
+ if (rightNode.left != null) {
+ rightNode.left.parent = rightNode;
+ }
+ rightNode.recalculateHeight();
+ updateParent(leftRightNode, node);
+ leftRightNode.right = rightNode;
+ rightNode.parent = leftRightNode;
+ leftRightNode.left = node;
+ node.parent = leftRightNode;
+ leftRightNode.recalculateHeight();
+ }
+
+ /** Update two-way parent-child reference to a new child */
+ private void updateParent(AVLNode newChild, AVLNode oldChild) {
+ newChild.parent = oldChild.parent;
+ if (oldChild.parent != null) {
+ if (oldChild.parent.left == oldChild) {
+ oldChild.parent.left = newChild;
+ } else {
+ oldChild.parent.right = newChild;
+ }
+ } else {
+ root = newChild;
+ }
+ }
+
+ @Override
+ protected AVLNode removeNode(Node node) {
+ AVLNode removedNode = (AVLNode) super.removeNode(node);
+ updateBalance(removedNode.getParent());
+ return removedNode;
+ }
+
+
}
Modified: trunk/src/net/sourceforge/geom4j/util/AVLTreeTest.java
===================================================================
--- trunk/src/net/sourceforge/geom4j/util/AVLTreeTest.java 2009-12-10 21:16:10 UTC (rev 18)
+++ trunk/src/net/sourceforge/geom4j/util/AVLTreeTest.java 2009-12-14 22:57:47 UTC (rev 19)
@@ -16,7 +16,10 @@
package net.sourceforge.geom4j.util;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import java.util.Random;
+
import org.junit.Before;
import org.junit.Test;
@@ -31,7 +34,6 @@
return new AVLTree<Integer>();
}
-
@Test
public void testHeight() {
SearchTree<Integer> t = createBinaryTree(
@@ -39,10 +41,70 @@
assertEquals(4, t.height());
t = createBinaryTree(
+ new Integer[] {1, 2, 3});
+ assertEquals(2, t.height());
+
+ t = createBinaryTree(
new Integer[] {7, 11, 17, 23, 15, 35});
assertEquals(3, t.height());
t = createEmptyBinaryTree();
assertEquals(0, t.height());
}
+
+ @Test
+ public void testRandomTreeInBalance() {
+ Integer[] values = createRandomUniqueArray(3000, 10000);
+ AVLTree<Integer> t = (AVLTree<Integer>) createBinaryTree(values);
+ assertTrue(t.isBalanced());
+ }
+
+ @Test
+ public void testRandomTreeHeight() {
+ Integer[] values = createRandomUniqueArray(3000, 10000);
+ SearchTree<Integer> simple = new BinarySearchTree<Integer>();
+ SearchTree<Integer> balanced = new AVLTree<Integer>();
+ populateTree(simple, values);
+ populateTree(balanced, values);
+ assertTrue(balanced.height() < simple.height());
+ }
+
+ @Test
+ public void testRemoval() {
+ SearchTree<Integer> t = createBinaryTree(
+ new Integer[] {8,
+ 4, 12,
+ 2, 6, 10, 14,
+ 1, 3, 5, 7, 9, 11, 13, 15});
+ for (Integer i: new Integer[] {6, 5, 7, 1, 2, 4, 3, 13,
+ 15, 14, 9, 8, 11, 10, 12}) {
+ t.remove(i);
+ assertTrue("Became unbalanced after removing " + i, t.isBalanced());
+ }
+ assertEquals(0, t.size());
+ assertEquals(0, t.height());
+ }
+
+ @Test
+ public void testBalanceInvariantOnRandomOperations() {
+ int maxN = 1000; // values picked from [0..maxN) interval
+ int n = 10000; // number of insertions/deletions
+ int k = 100; // bulk size
+ SearchTree<Integer> t = createEmptyBinaryTree();
+ Random r = new Random();
+ for (int i = 0; i < n/k; i++) {
+ if (r.nextBoolean()) {
+ for (int j = 0; j < k; j++) {
+ t.insert(r.nextInt(maxN));
+ assertTrue(t.isBalanced());
+ }
+ } else {
+ for (int j = 0; j < k; j++) {
+ t.remove(r.nextInt(maxN));
+ assertTrue(t.isBalanced());
+ }
+ }
+ //System.out.println(t.size() + ", " + t.height());
+ }
+ }
}
Modified: trunk/src/net/sourceforge/geom4j/util/BinarySearchTree.java
===================================================================
--- trunk/src/net/sourceforge/geom4j/util/BinarySearchTree.java 2009-12-10 21:16:10 UTC (rev 18)
+++ trunk/src/net/sourceforge/geom4j/util/BinarySearchTree.java 2009-12-14 22:57:47 UTC (rev 19)
@@ -51,6 +51,39 @@
this.value = value;
this.parent = parent;
}
+
+ public E getValue() {
+ return value;
+ }
+
+ public void setValue(E value) {
+ this.value = value;
+ }
+
+ public Node getLeft() {
+ return left;
+ }
+
+ public void setLeft(Node left) {
+ this.left = left;
+ }
+
+ public Node getRight() {
+ return right;
+ }
+
+ public void setRight(Node right) {
+ this.right = right;
+ }
+
+ public Node getParent() {
+ return parent;
+ }
+
+ public void setParent(Node parent) {
+ this.parent = parent;
+ }
+
}
/** Root of the tree */
@@ -98,7 +131,7 @@
*/
public boolean insert(E e) {
if (root == null) {
- root = new Node(e, null);
+ root = createNode(e, null);
return true;
}
Node node = root;
@@ -115,15 +148,21 @@
node = node.right;
}
} while (node != null);
+ insertChildNode(e, parent, c);
+ return true;
+ }
+
+ protected Node insertChildNode(E e, Node parent, int c) {
+ Node childNode = createNode(e, parent);
if (c < 0) {
- parent.left = insertNode(e, parent);
+ parent.left = childNode;
} else {
- parent.right = insertNode(e, parent);
+ parent.right = childNode;
}
- return true;
+ return childNode;
}
- protected Node insertNode(E e, Node parent) {
+ protected Node createNode(E e, Node parent) {
return new Node(e, parent);
}
@@ -151,14 +190,10 @@
return false;
}
- protected void removeNode(Node node) {
- if (node.left == null && node.right == null) { // remove node
- updateNodeReferences(node, null);
- } else if (node.left == null){ // replace node with its single right child
- updateNodeReferences(node, node.right);
- } else if (node.right == null){ // replace node with its single left child
- updateNodeReferences(node, node.left);
- } else {
+ /** Return the node which was actually removed */
+ protected Node removeNode(Node node) {
+ Node removed = removeNodeInPlace(node);
+ if (removed == null) {
/* We can either replace the node with the biggest element of its
* left subtree or with the smallest element of its right subtree.
* We randomize the selection of this element to prevent tree
@@ -170,17 +205,33 @@
Node predecessor = previousNode(node);
node.value = predecessor.value;
// remove predecessor (it can have at most one child)
- removeNode(predecessor);
+ removed = removeNodeInPlace(predecessor);
} else {
// replace node value with its in-order successor's value
Node successor = nextNode(node);
node.value = successor.value;
// remove successor (it can have at most one child)
- removeNode(successor);
+ removed = removeNodeInPlace(successor);
}
}
+ return removed;
}
+ /** Tries to remove the node specified if it doesn't have one of the children */
+ protected Node removeNodeInPlace(Node node) {
+ if (node.left != null && node.right != null) {
+ return null;
+ }
+ if (node.left == null && node.right == null) { // remove node
+ updateNodeReferences(node, null);
+ } else if (node.left == null){ // replace node with its single right child
+ updateNodeReferences(node, node.right);
+ } else if (node.right == null){ // replace node with its single left child
+ updateNodeReferences(node, node.left);
+ }
+ return node;
+ }
+
private void updateNodeReferences(Node node, Node replacement) {
if (node.parent == null) { // node == root
root = replacement;
@@ -374,4 +425,22 @@
public boolean isEmpty() {
return root == null;
}
+
+ /**
+ * Check if the tree is balanced,
+ * i.e. balance factor of each node is between -1 and +1.
+ */
+ @Override
+ public boolean isBalanced() {
+ return isNodeBalanced(root);
+ }
+
+ private boolean isNodeBalanced(Node node) {
+ if (node == null) {
+ return true;
+ }
+ return isNodeBalanced(node.left) && isNodeBalanced(node.right)
+ && Math.abs(heightNode(node.left) - heightNode(node.right)) <= 1;
+ }
+
}
Modified: trunk/src/net/sourceforge/geom4j/util/BinarySearchTreeTestBase.java
===================================================================
--- trunk/src/net/sourceforge/geom4j/util/BinarySearchTreeTestBase.java 2009-12-10 21:16:10 UTC (rev 18)
+++ trunk/src/net/sourceforge/geom4j/util/BinarySearchTreeTestBase.java 2009-12-14 22:57:47 UTC (rev 19)
@@ -17,7 +17,9 @@
import static org.junit.Assert.*;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -74,13 +76,33 @@
}
@Test
- public void testRandomTree() {
+ public void testRandomTreeInsertion() {
Integer[] values = createRandomUniqueArray(3000, 10000);
SearchTree<Integer> t = createBinaryTree(values);
Arrays.sort(values);
assertTrue(listsEqual(values, t.toList()));
assertEquals(values.length, t.size());
}
+
+ @Test
+ public void testRandomTreeRemoval() {
+ int maxN = 10000; // values inserted from [0..maxN) interval
+ int n = 3000; // number of insertions
+ int k = 2500; // number of deletions
+ Integer[] ints = createRandomUniqueArray(n, maxN);
+ SearchTree<Integer> t = createBinaryTree(ints);
+ ArrayList<Integer> values = new ArrayList<Integer>(ints.length);
+ Collections.addAll(values, ints);
+ Collections.sort(values);
+ Random r = new Random();
+ for (int i = 0; i < k; i++) {
+ int j = r.nextInt(values.size());
+ t.remove(values.get(j));
+ values.remove(j);
+ }
+ assertTrue(listsEqual(values.toArray(new Integer[0]), t.toList()));
+ assertEquals(values.size(), t.size());
+ }
@Test
public void testSize() {
@@ -131,7 +153,7 @@
return true;
}
- private Integer[] createRandomUniqueArray(int size, int maxValue) {
+ protected Integer[] createRandomUniqueArray(int size, int maxValue) {
Random r = new Random();
Set<Integer> used = new HashSet<Integer>();
Integer[] values = new Integer[size];
@@ -158,7 +180,7 @@
*/
protected abstract SearchTree<Integer> createEmptyBinaryTree();
- private void populateTree(SearchTree<Integer> tree, Integer[] values) {
+ protected void populateTree(SearchTree<Integer> tree, Integer[] values) {
for (int i: values) {
tree.insert(i);
}
Modified: trunk/src/net/sourceforge/geom4j/util/SearchTree.java
===================================================================
--- trunk/src/net/sourceforge/geom4j/util/SearchTree.java 2009-12-10 21:16:10 UTC (rev 18)
+++ trunk/src/net/sourceforge/geom4j/util/SearchTree.java 2009-12-14 22:57:47 UTC (rev 19)
@@ -68,4 +68,7 @@
/** Check if tree contains elements */
boolean isEmpty();
+
+ /** Check if the tree is balanced */
+ boolean isBalanced();
}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|