One thing's for sure: this is not an easy problem. Here's my approach.
First things first, let's get the recursion straight. What I'd like to be able to do is print the left subtree of the node, then print the right subtree, then somehow combine those two to get my final result. To do this, I'm going to need a data class for those intermediate values:
public class TreeString {
private List<String> lines;
private int columnCount;
private int rootColumn;
public TreeString(List<String> lines, int columnCount, int rootColumn) {
this.lines = new ArrayList<>(lines);
this.columnCount = columnCount;
this.rootColumn = rootColumn;
}
/** @return the number of lines */
public int getLineCount() {
return lines.size();
}
/** @return the number of columns */
public int getColumnCount() {
return columnCount;
}
/** @return the index of the column containing the center of the root */
public int getRootColumn() {
return rootColumn;
}
/** @return the number of columns to the right of the column containing the center of the root */
public int getRootColumnFromRight() {
return getColumnCount() - (getRootColumn() + 1);
}
/** @return the line at {@code index} */
public String getLine(int index) {
return lines.get(index);
}
}
So, for instance, this tree
4
/ \
2 5
/ \
1 3
would be represented by this TreeString:
lines = new ArrayList<>(Arrays.asList(" 4 ", " / \\ ", " 2 5", " / \\ ", "1 3 "));
columnCount = 7;
rootColumn = 4;
Notice that all lines have the same length. This will be important later on.
OK, so how do we implement printTree? Well, it's very simple: we kill the Batman write some boilerplate.
public void printTree(BSTNode node, int depth) {
if (depth > 0) {
TreeString treeString = treeStringFromBSTNode(node, depth);
for (int i = 0; i < treeString.getLineCount(); ++i) {
System.out.println(treeString.getLine(i));
}
}
}
public TreeString treeStringFromString(String string) {
return new TreeString(Collections.singletonList(string), string.length(), string.length() / 2);
}
public TreeString treeStringFromBSTNode(BSTNode node, int depth) {
TreeString value = treeStringFromString(String.valueOf(node.getValue()));
TreeString left = depth <= 1 || node.getLeft() == null ? null : treeStringFromBSTNode(node.getLeft(), depth - 1);
TreeString right = depth <= 1 || node.getRight() == null ? null : treeStringFromBSTNode(node.getRight(), depth - 1);
return combineTreeStrings(value, left, right);
}
Now, on to the main event:
public String spaces(int numSpaces) {
String string = "";
for (int i = 0; i < numSpaces; ++i) {
string += " ";
}
return string;
}
public TreeString combineTreeStrings(TreeString parent, TreeString left, TreeString right) {
if (left == null && right == null) {
return parent;
}
// the number of lines between the bottom of parent and the tops of left and right
// also the number of columns between parent's root column and the root columns of left or right
int verticalGap = 1;
// the number of columns between the left end of right and the right end of left
int middleGap = 0;
if (left != null && right != null) {
verticalGap = Math.max(verticalGap, (left.getRootColumnFromRight() + 1 + right.getRootColumn()) / 2);
middleGap = (verticalGap - left.getRootColumnFromRight()) + 1 + (verticalGap - right.getRootColumn());
}
// the number of columns between the left end of left (or right, if left is null) and the left end of the result
int lowerLeftGap;
// the number of columns between the left end of parent and the left end of the result
int upperLeftGap;
if (left != null) {
lowerLeftGap = Math.max(0, parent.getRootColumn() - verticalGap - 1 - left.getRootColumn());
upperLeftGap = Math.max(0, left.getRootColumn() + 1 + verticalGap - parent.getRootColumn());
} else {
lowerLeftGap = Math.max(0, parent.getRootColumn() + 1 + verticalGap - right.getRootColumn());
upperLeftGap = Math.max(0, right.getRootColumn() - verticalGap - 1 - parent.getRootColumn());
}
// the number of columns between the right end of the result and the right end of right (or left, if right is null)
int lowerRightGap;
// the number of columns between the right end of the result and the right end of parent
int upperRightGap;
if (right != null) {
lowerRightGap = Math.max(0, -right.getRootColumnFromRight() - 1 - verticalGap + parent.getRootColumnFromRight());
upperRightGap = Math.max(0, -parent.getRootColumnFromRight() + verticalGap + 1 + right.getRootColumnFromRight());
} else {
lowerRightGap = Math.max(0, -left.getRootColumnFromRight() + verticalGap + 1 + parent.getRootColumnFromRight());
upperRightGap = Math.max(0, -parent.getRootColumnFromRight() - 1 - verticalGap + left.getRootColumnFromRight());
}
List<String> lines = new ArrayList<>();
// parent lines
for (int i = 0; i < parent.getLineCount(); ++i) {
lines.add(spaces(upperLeftGap) + parent.getLine(i) + spaces(upperRightGap));
}
// slash and backslash lines
for (int i = 0; i < verticalGap; ++i) {
String leftLeg;
if (left != null) {
leftLeg = "/";
} else if (upperLeftGap > 0) {
leftLeg = " ";
} else {
leftLeg = "";
}
String rightLeg;
if (right != null) {
rightLeg = "\\";
} else if (upperRightGap > 0) {
rightLeg = " ";
} else {
rightLeg = "";
}
int numLeftSpaces = upperLeftGap + parent.getRootColumn() - leftLeg.length() - i;
int numRightSpaces = upperRightGap + parent.getRootColumnFromRight() - rightLeg.length() - i;
lines.add(spaces(numLeftSpaces) + leftLeg + spaces(i + 1 + i) + rightLeg + spaces(numRightSpaces));
}
// left and right lines
for (int i = 0; i < Math.max(left == null ? 0 : left.getLineCount(), right == null ? 0 : right.getLineCount()); ++i) {
String leftLine;
if (left == null) {
leftLine = "";
} else if (i >= left.getLineCount()) {
leftLine = spaces(left.getColumnCount());
} else {
leftLine = left.getLine(i);
}
String rightLine;
if (right == null) {
rightLine = "";
} else if (i >= right.getLineCount()) {
rightLine = spaces(right.getColumnCount());
} else {
rightLine = right.getLine(i);
}
lines.add(spaces(lowerLeftGap) + leftLine + spaces(middleGap) + rightLine + spaces(lowerRightGap));
}
return new TreeString(lines, upperLeftGap + parent.getColumnCount() + upperRightGap, upperLeftGap + parent.getRootColumn());
}
Hopefully this solves your problem! If there's any way this can be cleaned up, don't hesitate to comment.
5had a left child?Visitordesign pattern and adding the associatedvisitmethod to yourBSTNodeclass should make implementing output in the format you have described more straightforward. TheVisitorwill then be able to traverse the nodes, iterate across each level, and have the context with respect to progress through the traversal and iteration that is needed to get the formatting implemented correctly.