I was inspired by this answer and decided to implement an AVL tree with the methods equals
and hashCode
as if I was asked to do that at a job interview.
This is my code on GitHub.
In addition to the solution itself, I wrote tests for all the possible cases. If I missed something, or if my solution has any bugs, please let me know.
AvlTree
package com.bst;
import java.util.LinkedList;
public class AvlTree {
Node mRoot;
public AvlTree() {
}
public AvlTree(int root) {
mRoot = new Node(root);
}
private Node insert(Node root, int key) {
if (root == null) {
return new Node(key);
}
if (key < root.mValue) {
root.mLeft = insert(root.mLeft, key);
} else {
root.mRight = insert(root.mRight, key);
}
return balance(root);
}
private Node balance(Node p) {
fixHeight(p);
if (bfactor(p) == 2) {
if (bfactor(p.mRight) < 0) {
p.mRight = rotateRight(p.mRight);
}
return rotateLeft(p);
}
if (bfactor(p) == -2) {
if (bfactor(p.mLeft) > 0) {
p.mLeft = rotateLeft(p.mLeft);
}
return rotateRight(p);
}
return p;
}
private Node rotateRight(Node p) {
Node q = p.mLeft;
p.mLeft = q.mRight;
q.mRight = p;
fixHeight(p);
fixHeight(q);
return q;
}
private Node rotateLeft(Node q) {
Node p = q.mRight;
q.mRight = p.mLeft;
p.mLeft = q;
fixHeight(q);
fixHeight(p);
return p;
}
private int height(Node p) {
return p == null ? 0 : p.mHeight;
}
private int bfactor(Node p) {
return height(p.mRight) - height(p.mLeft);
}
private void fixHeight(Node p) {
int hl = height(p.mLeft);
int hr = height(p.mRight);
p.mHeight = (hl > hr ? hl : hr) + 1;
}
public void insert(int... keys) {
for (int value : keys) {
mRoot = insert(mRoot, value);
}
}
@Override
public boolean equals(Object arg0) {
if (this == arg0) {
return true;
}
if (!(arg0 instanceof AvlTree)) {
return false;
}
AvlTree another = (AvlTree) arg0;
return areTreesEqual(this.mRoot, another.mRoot);
}
private boolean areTreesEqual(Node root1, Node root2) {
if (root1 == root2) {
return true;
}
if (root1 == null || root2 == null) {
return false;
}
return root1.mValue == root2.mValue && areTreesEqual(root1.mLeft, root2.mLeft) && areTreesEqual(root1.mRight, root2.mRight);
}
@Override
public int hashCode() {
if (mRoot == null) {
return 0;
}
LinkedList<Node> nodes = new LinkedList<AvlTree.Node>();
nodes.add(mRoot);
int res = 17;
while (!nodes.isEmpty()) {
Node head = nodes.remove();
res = 31 * res + head.hashCode();
if (head.mLeft != null) {
nodes.addLast(head.mLeft);
}
if (head.mRight != null) {
nodes.addLast(head.mRight);
}
}
return res;
}
@Override
public String toString() {
if (mRoot == null) {
return "[]";
}
StringBuilder builder = new StringBuilder("[");
inOrderPrint(mRoot, builder);
builder.setLength(builder.length() - 2);
builder.append("]");
return builder.toString();
}
private void inOrderPrint(Node root, StringBuilder builder) {
if (root != null) {
inOrderPrint(root.mLeft, builder);
builder.append(root + ", ");
inOrderPrint(root.mRight, builder);
}
}
static class Node {
Node mLeft;
Node mRight;
final int mValue;
private int mHeight;
private Node(int value) {
mValue = value;
mHeight = 1;
}
@Override
public int hashCode() {
int res = 17;
res = 17 * res + mValue;
return res;
}
@Override
public String toString() {
return Integer.toString(mValue);
}
}
}
AvlTreeTest
package com.bst;
import org.junit.Assert;
import org.junit.Test;
public class AvlTreeTest {
@Test
public void testDefaultConstructor() {
AvlTree t1 = new AvlTree();
Assert.assertNull(t1.mRoot);
}
@Test
public void testIntegerConstructor() {
AvlTree t1 = new AvlTree(1);
Assert.assertNotNull(t1.mRoot);
}
@Test
public void testInsertToEmptyTree() {
AvlTree t1 = new AvlTree();
t1.insert(1);
Assert.assertEquals(1, t1.mRoot.mValue);
}
@Test
public void testEqualsItself() {
AvlTree t1 = new AvlTree();
Assert.assertEquals(t1, t1);
}
@Test
public void testNotEqualNotAvlInstance() {
AvlTree t1 = new AvlTree();
Object object = new Object();
Assert.assertNotEquals(t1, object);
}
@Test
public void testEmptyEqual() {
AvlTree t1 = new AvlTree();
AvlTree t2 = new AvlTree();
Assert.assertEquals(t1, t2);
}
@Test
public void testFirstEmpty() {
AvlTree t1 = new AvlTree();
AvlTree t2 = new AvlTree(1);
Assert.assertNotEquals(t1, t2);
}
@Test
public void testSecondEmpty() {
AvlTree t1 = new AvlTree(1);
AvlTree t2 = new AvlTree();
Assert.assertNotEquals(t1, t2);
}
@Test
public void testRootsEqual() {
AvlTree t1 = new AvlTree(1);
AvlTree t2 = new AvlTree(1);
Assert.assertEquals(t1, t2);
}
@Test
public void testRootAndLeftEqual() {
AvlTree t1 = new AvlTree(10);
t1.insert(2);
AvlTree t2 = new AvlTree(10);
t2.insert(2);
Assert.assertEquals(t1, t2);
}
@Test
public void testRootAndRightEqual() {
AvlTree t1 = new AvlTree(1);
t1.insert(2);
AvlTree t2 = new AvlTree(1);
t2.insert(2);
Assert.assertEquals(t1, t2);
}
@Test
public void testRootsEqual_LeftsNotEqual() {
AvlTree t1 = new AvlTree(10);
t1.insert(2);
AvlTree t2 = new AvlTree(10);
t2.insert(1);
Assert.assertNotEquals(t1, t2);
}
@Test
public void testRootsEqual_RightsNotEqual() {
AvlTree t1 = new AvlTree(1);
t1.insert(2);
AvlTree t2 = new AvlTree(1);
t2.insert(4);
Assert.assertNotEquals(t1, t2);
}
@Test
public void testEmptyTreeHashCode() {
AvlTree t1 = new AvlTree();
Assert.assertEquals(0, t1.hashCode());
}
@Test
public void testEqualTreesEqualHashCodes() {
AvlTree t1 = new AvlTree(10);
t1.insert(2, 12);
AvlTree t2 = new AvlTree(10);
t2.insert(2, 12);
Assert.assertEquals(t1.hashCode(), t2.hashCode());
}
@Test
public void testToStringEmpty() {
AvlTree t1 = new AvlTree();
Assert.assertEquals("[]", t1.toString());
}
@Test
public void testToStringSingleNode() {
AvlTree t1 = new AvlTree(1);
Assert.assertEquals("[1]", t1.toString());
}
@Test
public void testToStringManyNodes() {
AvlTree t1 = new AvlTree(1);
t1.insert(12, 56, 7, 2, 1);
Assert.assertEquals("[1, 1, 2, 7, 12, 56]", t1.toString());
}
@Test
public void testSingleRotateLeft() {
AvlTree t1 = new AvlTree(10);
t1.insert(14, 56);
Assert.assertEquals(t1.mRoot.mValue, 14);
Assert.assertEquals(t1.mRoot.mLeft.mValue, 10);
Assert.assertEquals(t1.mRoot.mRight.mValue, 56);
}
@Test
public void testSingleRotateRight() {
AvlTree t1 = new AvlTree(10);
t1.insert(2, 1);
Assert.assertEquals(t1.mRoot.mValue, 2);
Assert.assertEquals(t1.mRoot.mLeft.mValue, 1);
Assert.assertEquals(t1.mRoot.mRight.mValue, 10);
}
@Test
public void testDoubleRotateLeftRight() {
AvlTree t1 = new AvlTree(10);
t1.insert(4, 9);
Assert.assertEquals(t1.mRoot.mValue, 9);
Assert.assertEquals(t1.mRoot.mLeft.mValue, 4);
Assert.assertEquals(t1.mRoot.mRight.mValue, 10);
}
@Test
public void testDoubleRotateRightLeft() {
AvlTree t1 = new AvlTree(10);
t1.insert(14, 12);
Assert.assertEquals(t1.mRoot.mValue, 12);
Assert.assertEquals(t1.mRoot.mLeft.mValue, 10);
Assert.assertEquals(t1.mRoot.mRight.mValue, 14);
}
}
branchFactor()
instead ofbFactor()
. \$\endgroup\$bFactor
is a balance factor \$\endgroup\$