public boolean deleteNode(E data) {
        BinaryTreeNode<E> node = findNode(data);
        if (node == null) {
            return false;
        }
        if (node.left == null && node.right == null) {
            // Deleting the leaf node
            replaceNodeWithChild(node, null);
        } else if (node.left == null || node.right == null) {
            // Deleting node with one child
            if (node.left != null) {
                replaceNodeWithChild(node, node.left);
            } else {
                replaceNodeWithChild(node, node.right);
            }
        } else {
            // Deleting node with two children. Get either in-order successor or in-order predecessor (anything is
            // fine). For now working with predecessor, better would be to check the height of the left and right
            // subtree and call predecessor or successor accordingly
            BinaryTreeNode<E> predecessor = BinarySearchTreeAlgorithms.inOrderPredecessor(node);
            E temp = predecessor.data;
            deleteNode(predecessor.data);
            node.data = temp;
        }
        return true;
    }