Just wondering if I can get some tips on printing a pretty binary tree in the form of:
5
10
11
7
6
3
4
Here's a little example for printing out an array based heap in tree form. It would need a little adjusting to the algorithm for bigger numbers. I just made a grid on paper and figured out what space index each node would be to look nice, then noticed there was a pattern to how many spaces each node needed based on its parent's number of spaces and the level of recursion as well as how tall the tree is. This solution goes a bit beyond just printing in level order and satisfies the "beauty" requirement.
#include <iostream>
#include <vector>
static const int g_TerminationNodeValue = -999;
class HeapJ
{
public:
HeapJ(int* pHeapArray, int numElements)
{
m_pHeapPointer = pHeapArray;
m_numElements = numElements;
m_treeHeight = GetTreeHeight(1);
}
void Print()
{
m_printVec.clear();
int initialIndex = 0;
for(int i=1; i<m_treeHeight; ++i)
{
int powerOfTwo = 1;
for(int j=0; j<i; ++j)
{
powerOfTwo *= 2;
}
initialIndex += powerOfTwo - (i-1);
}
DoPrintHeap(1,0,initialIndex);
for(size_t i=0; i<m_printVec.size(); ++i)
{
std::cout << m_printVec[i] << '\n' << '\n';
}
}
private:
int* m_pHeapPointer;
int m_numElements;
int m_treeHeight;
std::vector<std::string> m_printVec;
int GetTreeHeight(int index)
{
const int value = m_pHeapPointer[index-1];
if(value == g_TerminationNodeValue)
{
return -1;
}
const int childIndexLeft = 2*index;
const int childIndexRight = childIndexLeft+1;
int valLeft = 0;
int valRight = 0;
if(childIndexLeft <= m_numElements)
{
valLeft = GetTreeHeight(childIndexLeft);
}
if(childIndexRight <= m_numElements)
{
valRight = GetTreeHeight(childIndexRight);
}
return std::max(valLeft,valRight)+1;
}
void DoPrintHeap(int index, size_t recursionLevel, int numIndents)
{
const int value = m_pHeapPointer[index-1];
if(value == g_TerminationNodeValue)
{
return;
}
if(m_printVec.size() == recursionLevel)
{
m_printVec.push_back(std::string(""));
}
const int numLoops = numIndents - (int)m_printVec[recursionLevel].size();
for(int i=0; i<numLoops; ++i)
{
m_printVec[recursionLevel].append(" ");
}
m_printVec[recursionLevel].append(std::to_string(value));
const int childIndexLeft = 2*index;
const int childIndexRight = childIndexLeft+1;
const int exponent = m_treeHeight-(recursionLevel+1);
int twoToPower = 1;
for(int i=0; i<exponent; ++i)
{
twoToPower *= 2;
}
const int recursionAdjust = twoToPower-(exponent-1);
if(childIndexLeft <= m_numElements)
{
DoPrintHeap(childIndexLeft, recursionLevel+1, numIndents-recursionAdjust);
}
if(childIndexRight <= m_numElements)
{
DoPrintHeap(childIndexRight, recursionLevel+1, numIndents+recursionAdjust);
}
}
};
const int g_heapArraySample_Size = 14;
int g_heapArraySample[g_heapArraySample_Size] = {16,14,10,8,7,9,3,2,4,1,g_TerminationNodeValue,g_TerminationNodeValue,g_TerminationNodeValue,0};
int main()
{
HeapJ myHeap(g_heapArraySample,g_heapArraySample_Size);
myHeap.Print();
return 0;
}
/* output looks like this:
16
14 10
8 7 9 3
2 4 1 0
*/
Late late answer and its in Java, but I'd like to add mine to the record because I found out how to do this relatively easily and the way I did it is more important. The trick is to recognize that what you really want is for none of your sub-trees to be printed directly under your root/subroot nodes (in the same column). Why you might ask? Because it Guarentees that there are no spacing problems, no overlap, no possibility of the left subtree and right subtree ever colliding, even with superlong numbers. It auto adjusts to the size of your node data. The basic idea is to have the left subtree be printed totally to the left of your root and your right subtree is printed totally to the right of your root.
A good way to think about it is with Umbrellas, Imagine first that you are outside with a large umbrella, you represent the root and your Umbrella and everything under it is the whole tree. think of your left subtree as a short man (shorter than you anyway) with a smaller umbrella who is on your left under your large umbrella. Your right subtree is represented by a similar man with a similarly smaller umbrella on your right side. Imagine that if the umbrellas of the short men ever touch, they get angry and hit each other (bad overlap). You are the root and the men beside you are your subtrees. You must be exactly in the middle of their umbrellas (subtrees) to break up the two men and ensure they never bump umbrellas. The trick is to then imagine this recursively, where each of the two men each have their own two smaller people under their umbrella (children nodes) with ever smaller umbrellas (sub-subtrees and so-on) that they need to keep apart under their umbrella (subtree), They act as sub-roots. Fundamentally, thats what needs to happen to 'solve' the general problem when printing binary trees, subtree overlap. To do this, you simply need to think about how you would 'print' or 'represent' the men in my anaolgy.
Firstly the only reason my code implementation takes in more parameters than should be needed (currentNode to be printed and node level) is because I can't easily move a line up in console when printing, so I have to map my lines first and print them in reverse. To do this I made a lineLevelMap that mapped each line of the tree to it's output (this might be useful for the future as a way to easily gather every line of the tree and also print it out at the same time).
//finds the height of the tree beforehand recursively, left to reader as exercise
int height = TreeHeight(root);
//the map that uses the height of the tree to detemrine how many entries it needs
//each entry maps a line number to the String of the actual line
HashMap<Integer,String> lineLevelMap = new HashMap<>();
//initialize lineLevelMap to have the proper number of lines for our tree
//printout by starting each line as the empty string
for (int i = 0; i < height + 1; i++) {
lineLevelMap.put(i,"");
}
If I could get ANSI escape codes working in the java console (windows ugh) I could simply print one line upwards and I would cut my parameter count by two because I wouldn't need to map lines or know the depth of the tree beforehand. Regardless here is my code that recurses in an in-order traversal of the tree:
public int InOrderPrint(CalcTreeNode currentNode, HashMap<Integer,String>
lineLevelMap, int level, int currentIndent){
//traverse left case
if(currentNode.getLeftChild() != null){
//go down one line
level--;
currentIndent =
InOrderPrint(currentNode.getLeftChild(),lineLevelMap,level,currentIndent);
//go up one line
level++;
}
//find the string length that already exists for this line
int previousIndent = lineLevelMap.get(level).length();
//create currentIndent - previousIndent spaces here
char[] indent = new char[currentIndent-previousIndent];
Arrays.fill(indent,' ');
//actually append the nodeData and the proper indent to add on to the line
//correctly
lineLevelMap.put(level,lineLevelMap.get(level).concat(new String(indent) +
currentNode.getData()));
//update the currentIndent for all lines
currentIndent += currentNode.getData().length();
//traverse right case
if (currentNode.getRightChild() != null){
//go down one line
level--;
currentIndent =
InOrderPrint(currentNode.getRightChild(),lineLevelMap,level,currentIndent);
//go up one line
level++;
}
return currentIndent;
}
To actually print this Tree to console in java, just use the LineMap that we generated. This way we can print the lines right side up
for (int i = height; i > -1; i--) {
System.out.println(lineLevelMap.get(i));
}
The InorderPrint sub function does all the 'work' and can recursively print out any Node and it's subtrees properly. Even better, it spaces them evenly and you can easily modify it to space out all nodes equally (just make the Nodedata equal or make the algorithim think it is). The reason it works so well is because it uses the Node's data length to determine where the next indent should be. This assures that the left subtree is always printed BEFORE the root and the right subtree, thus if you ensure this recursively, no left node is printed under it's root nor its roots root and so-on with the same thing true for any right node. Instead the root and all subroots are directly in the middle of their subtrees and no space is wasted.
An example output with an input of 3 + 2 looks like in console is:
And an example of 3 + 4 * 5 + 6 is:
And finally an example of ( 3 + 4 ) * ( 5 + 6 ) note the parenthesis is:
The reason an Inorder traversal works so well is because it Always prints the leftmost stuff first, then the root, then the rightmost stuff. Exactly how we want our subtrees to be: everything to the left of the root is printed to the left of the root, everything to the right is printed to the right. Inorder traversal naturally allows for this relationship, and since we print lines and make indents based on nodeData, we don't need to worry about the length of our data. The node could be 20 characters long and it wouldn't affect the algorithm (although you might start to run out of actual screen space). The algorithm doesn't create any spacing between nodes but that can be easily implemented, the important thing is that they don't overlap.
Just to prove it for you (don't take my word for this stuff) here is an example with some quite long characters
As you can see, it simply adjusts based on the size of the data, No overlap! As long as your screen is big enough. If anyone ever figures out an easy way to print one line up in the java console (I'm all ears) This will become much much simpler, easy enough for almost anyone with basic knowledge of trees to understand and use, and the best part is there is no risk of bad overlapping errors.
Here is my code. It prints very well,maybe its not perfectly symmetrical. little description:
void Tree::TREEPRINT()
{
int i = 0;
while (i <= treeHeight(getroot())){
printlv(i);
i++;
cout << endl;
}
}
void Tree::printlv(int n){
Node* temp = getroot();
int val = pow(2, treeHeight(root) -n+2);
cout << setw(val) << "";
prinlv(temp, n, val);
}
void Tree::dispLV(Node*p, int lv, int d)
{
int disp = 2 * d;
if (lv == 0){
if (p == NULL){
cout << " x ";
cout << setw(disp -3) << "";
return;
}
else{
int result = ((p->key <= 1) ? 1 : log10(p->key) + 1);
cout << " " << p->key << " ";
cout << setw(disp - result-2) << "";
}
}
else
{
if (p == NULL&& lv >= 1){
dispLV(NULL, lv - 1, d);
dispLV(NULL, lv - 1, d);
}
else{
dispLV(p->left, lv - 1, d);
dispLV(p->right, lv - 1, d);
}
}
}
Input:
50-28-19-30-29-17-42-200-160-170-180-240-44-26-27
Output: https://i.stack.imgur.com/TtPXY.png