274 lines
9.4 KiB
Java
274 lines
9.4 KiB
Java
|
package DataStructures.Trees;
|
||
|
|
||
|
import java.util.Stack;
|
||
|
import java.util.HashMap;
|
||
|
|
||
|
/**
|
||
|
* This class will check if a BinaryTree is balanced.
|
||
|
* A balanced binary tree is defined as a binary tree where
|
||
|
* the differenced in height between the left and right
|
||
|
* subtree of each node differs by at most one.
|
||
|
*
|
||
|
* This can be done in both an iterative and recursive
|
||
|
* fashion. Below, `isBalancedRecursive()` is
|
||
|
* implemented in a recursive fashion, and
|
||
|
* `isBalancedIterative()` is implemented in an
|
||
|
* iterative fashion.
|
||
|
*
|
||
|
* @author [Ian Cowan](https://github.com/iccowan)
|
||
|
*/
|
||
|
public class CheckIfBinaryTreeBalanced {
|
||
|
|
||
|
/**
|
||
|
* This class implements the BinaryTree for these algorithms
|
||
|
*/
|
||
|
class BinaryTree {
|
||
|
/** The root node of the binary tree */
|
||
|
BTNode root = null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This class implements the nodes for the binary tree
|
||
|
*/
|
||
|
class BTNode {
|
||
|
/** The value of the node */
|
||
|
int value;
|
||
|
|
||
|
/** The left child of the node */
|
||
|
BTNode left = null;
|
||
|
|
||
|
/** The right child of the node */
|
||
|
BTNode right = null;
|
||
|
|
||
|
/** Constructor */
|
||
|
BTNode (int value) {
|
||
|
this.value = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Recursive is BT balanced implementation
|
||
|
*
|
||
|
* @param binaryTree The binary tree to check if balanced
|
||
|
*/
|
||
|
public boolean isBalancedRecursive(BinaryTree binaryTree) {
|
||
|
// Create an array of length 1 to keep track of our balance
|
||
|
// Default to true. We use an array so we have an efficient mutable object
|
||
|
boolean[] isBalanced = new boolean[1];
|
||
|
isBalanced[0] = true;
|
||
|
|
||
|
// Check for balance and return whether or not we are balanced
|
||
|
isBalancedRecursive(binaryTree.root, 0, isBalanced);
|
||
|
return isBalanced[0];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Private helper method to keep track of the depth and balance during
|
||
|
* recursion. We effectively perform a modified post-order traversal where
|
||
|
* we are looking at the heights of both children of each node in the tree
|
||
|
*
|
||
|
* @param node The current node to explore
|
||
|
* @param depth The current depth of the node
|
||
|
* @param isBalanced The array of length 1 keeping track of our balance
|
||
|
*/
|
||
|
private int isBalancedRecursive(BTNode node, int depth, boolean[] isBalanced) {
|
||
|
// If the node is null, we should not explore it and the height is 0
|
||
|
// If the tree is already not balanced, might as well stop because we
|
||
|
// can't make it balanced now!
|
||
|
if (node == null || ! isBalanced[0])
|
||
|
return 0;
|
||
|
|
||
|
// Visit the left and right children, incrementing their depths by 1
|
||
|
int leftHeight = isBalancedRecursive(node.left, depth + 1, isBalanced);
|
||
|
int rightHeight = isBalancedRecursive(node.right, depth + 1, isBalanced);
|
||
|
|
||
|
// If the height of either of the left or right subtrees differ by more
|
||
|
// than 1, we cannot be balanced
|
||
|
if (Math.abs(leftHeight - rightHeight) > 1)
|
||
|
isBalanced[0] = false;
|
||
|
|
||
|
// The height of our tree is the maximum of the heights of the left
|
||
|
// and right subtrees plus one
|
||
|
return Math.max(leftHeight, rightHeight) + 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Iterative is BT balanced implementation
|
||
|
*/
|
||
|
public boolean isBalancedIterative(BinaryTree binaryTree) {
|
||
|
// Default that we are balanced and our algo will prove it wrong
|
||
|
boolean isBalanced = true;
|
||
|
|
||
|
// Create a stack for our post order traversal
|
||
|
Stack<BTNode> nodeStack = new Stack<BTNode>();
|
||
|
|
||
|
// For post order traversal, we'll have to keep track of where we
|
||
|
// visited last
|
||
|
BTNode lastVisited = null;
|
||
|
|
||
|
// Create a HashMap to keep track of the subtree heights for each node
|
||
|
HashMap<BTNode, Integer> subtreeHeights = new HashMap<BTNode, Integer>();
|
||
|
|
||
|
// We begin at the root of the tree
|
||
|
BTNode node = binaryTree.root;
|
||
|
|
||
|
// We loop while:
|
||
|
// - the node stack is empty and the node we explore is null
|
||
|
// AND
|
||
|
// - the tree is still balanced
|
||
|
while (! (nodeStack.isEmpty() && node == null) && isBalanced) {
|
||
|
// If the node is not null, we push it to the stack and continue
|
||
|
// to the left
|
||
|
if (node != null) {
|
||
|
nodeStack.push(node);
|
||
|
node = node.left;
|
||
|
// Once we hit a node that is null, we are as deep as we can go
|
||
|
// to the left
|
||
|
} else {
|
||
|
// Find the last node we put on the stack
|
||
|
node = nodeStack.peek();
|
||
|
|
||
|
// If the right child of the node has either been visited or
|
||
|
// is null, we visit this node
|
||
|
if (node.right == null || node.right == lastVisited) {
|
||
|
// We assume the left and right heights are 0
|
||
|
int leftHeight = 0;
|
||
|
int rightHeight = 0;
|
||
|
|
||
|
// If the right and left children are not null, we must
|
||
|
// have already explored them and have a height
|
||
|
// for them so let's get that
|
||
|
if (node.left != null)
|
||
|
leftHeight = subtreeHeights.get(node.left);
|
||
|
|
||
|
if (node.right != null)
|
||
|
rightHeight = subtreeHeights.get(node.right);
|
||
|
|
||
|
// If the difference in the height of the right subtree
|
||
|
// and left subtree differs by more than 1, we cannot be
|
||
|
// balanced
|
||
|
if (Math.abs(rightHeight - leftHeight) > 1)
|
||
|
isBalanced = false;
|
||
|
|
||
|
// The height of the subtree containing this node is the
|
||
|
// max of the left and right subtree heighs plus 1
|
||
|
subtreeHeights.put(node, Math.max(rightHeight, leftHeight) + 1);
|
||
|
|
||
|
// We've now visited this node, so we pop it from the stack
|
||
|
nodeStack.pop();
|
||
|
lastVisited = node;
|
||
|
|
||
|
// Current visiting node is now null
|
||
|
node = null;
|
||
|
// If the right child node of this node has not been visited
|
||
|
// and is not null, we need to get that child node on the stack
|
||
|
} else {
|
||
|
node = node.right;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return whether or not the tree is balanced
|
||
|
return isBalanced;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates the following unbalanced binary tree for testing
|
||
|
* 0
|
||
|
* / \
|
||
|
* / \
|
||
|
* 0 0
|
||
|
* / / \
|
||
|
* / / \
|
||
|
* 0 0 0
|
||
|
* / \
|
||
|
* / \
|
||
|
* 0 0
|
||
|
* /
|
||
|
* /
|
||
|
* 0
|
||
|
*/
|
||
|
private BinaryTree buildUnbalancedTree() {
|
||
|
BinaryTree tree = new BinaryTree();
|
||
|
tree.root = new BTNode(0);
|
||
|
|
||
|
BTNode root = tree.root;
|
||
|
root.left = new BTNode(0);
|
||
|
root.right = new BTNode(0);
|
||
|
|
||
|
BTNode left = root.left;
|
||
|
BTNode right = root.right;
|
||
|
|
||
|
left.left = new BTNode(0);
|
||
|
right.left = new BTNode(0);
|
||
|
right.right = new BTNode(0);
|
||
|
right.left.right = new BTNode(0);
|
||
|
|
||
|
left = left.left;
|
||
|
left.left = new BTNode(0);
|
||
|
left.left.left = new BTNode(0);
|
||
|
left.left.left.left = new BTNode(0);
|
||
|
|
||
|
return tree;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates the following balanced binary tree for testing
|
||
|
* 0
|
||
|
* / \
|
||
|
* / \
|
||
|
* 0 0
|
||
|
* / \ / \
|
||
|
* / 0 / \
|
||
|
* 0 0 0
|
||
|
* / /
|
||
|
* / /
|
||
|
* 0 0
|
||
|
*/
|
||
|
private BinaryTree buildBalancedTree() {
|
||
|
BinaryTree tree = new BinaryTree();
|
||
|
tree.root = new BTNode(0);
|
||
|
|
||
|
BTNode root = tree.root;
|
||
|
root.left = new BTNode(0);
|
||
|
root.right = new BTNode(0);
|
||
|
|
||
|
BTNode left = root.left;
|
||
|
BTNode right = root.right;
|
||
|
|
||
|
left.left = new BTNode(0);
|
||
|
left.right = new BTNode(0);
|
||
|
right.left = new BTNode(0);
|
||
|
right.right = new BTNode(0);
|
||
|
|
||
|
right.right.left = new BTNode(0);
|
||
|
|
||
|
left.left.left = new BTNode(0);
|
||
|
|
||
|
return tree;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Main
|
||
|
*/
|
||
|
public static void main(String[] args) {
|
||
|
// We create a new object to check the binary trees for balance
|
||
|
CheckIfBinaryTreeBalanced balanceCheck = new CheckIfBinaryTreeBalanced();
|
||
|
|
||
|
// Build a balanced and unbalanced binary tree
|
||
|
BinaryTree balancedTree = balanceCheck.buildBalancedTree();
|
||
|
BinaryTree unbalancedTree = balanceCheck.buildUnbalancedTree();
|
||
|
|
||
|
// Run basic tests on the algorithms to check for balance
|
||
|
boolean isBalancedRB = balanceCheck.isBalancedRecursive(balancedTree); // true
|
||
|
boolean isBalancedRU = balanceCheck.isBalancedRecursive(unbalancedTree); // false
|
||
|
boolean isBalancedIB = balanceCheck.isBalancedIterative(balancedTree); // true
|
||
|
boolean isBalancedIU = balanceCheck.isBalancedIterative(unbalancedTree); // false
|
||
|
|
||
|
// Print the results
|
||
|
System.out.println("isBalancedRB: " + isBalancedRB);
|
||
|
System.out.println("isBalancedRU: " + isBalancedRU);
|
||
|
System.out.println("isBalancedIB: " + isBalancedIB);
|
||
|
System.out.println("isBalancedIU: " + isBalancedIU);
|
||
|
}
|
||
|
}
|