I have simple binary search tree
public class BNode
{
public int item;
public BNode right;
public BNode left;
public BNode(int item)
{
I've ended up with the following method that allows you to print arbitrary subtree:
public static class BTreePrinter
{
class NodeInfo
{
public BNode Node;
public string Text;
public int StartPos;
public int Size { get { return Text.Length; } }
public int EndPos { get { return StartPos + Size; } set { StartPos = value - Size; } }
public NodeInfo Parent, Left, Right;
}
public static void Print(this BNode root, string textFormat = "0", int spacing = 1, int topMargin = 2, int leftMargin = 2)
{
if (root == null) return;
int rootTop = Console.CursorTop + topMargin;
var last = new List();
var next = root;
for (int level = 0; next != null; level++)
{
var item = new NodeInfo { Node = next, Text = next.item.ToString(textFormat) };
if (level < last.Count)
{
item.StartPos = last[level].EndPos + spacing;
last[level] = item;
}
else
{
item.StartPos = leftMargin;
last.Add(item);
}
if (level > 0)
{
item.Parent = last[level - 1];
if (next == item.Parent.Node.left)
{
item.Parent.Left = item;
item.EndPos = Math.Max(item.EndPos, item.Parent.StartPos - 1);
}
else
{
item.Parent.Right = item;
item.StartPos = Math.Max(item.StartPos, item.Parent.EndPos + 1);
}
}
next = next.left ?? next.right;
for (; next == null; item = item.Parent)
{
int top = rootTop + 2 * level;
Print(item.Text, top, item.StartPos);
if (item.Left != null)
{
Print("/", top + 1, item.Left.EndPos);
Print("_", top, item.Left.EndPos + 1, item.StartPos);
}
if (item.Right != null)
{
Print("_", top, item.EndPos, item.Right.StartPos - 1);
Print("\\", top + 1, item.Right.StartPos - 1);
}
if (--level < 0) break;
if (item == item.Parent.Left)
{
item.Parent.StartPos = item.EndPos + 1;
next = item.Parent.Node.right;
}
else
{
if (item.Parent.Left == null)
item.Parent.EndPos = item.StartPos - 1;
else
item.Parent.StartPos += (item.StartPos - 1 - item.Parent.EndPos) / 2;
}
}
}
Console.SetCursorPosition(0, rootTop + 2 * last.Count - 1);
}
private static void Print(string s, int top, int left, int right = -1)
{
Console.SetCursorPosition(left, top);
if (right < 0) right = left + s.Length;
while (Console.CursorLeft < right) Console.Write(s);
}
}
As you can see, I've added some parameters that affect the formatting. By default it produces the most compact representation.
In order to play with it, I've modified the BTree
class as follows:
public class BTree
{
// ...
public BNode Root { get { return _root; } }
public void Print()
{
Root.Print();
}
}
Using your sample data, here are some results:
btr.Root.Print();
btr.Root.Print(textFormat: "(0)", spacing: 2);
UPDATE: IMO the default format above is compact and readable, but just for fun, adjusted the algorithm to produce more "graphical" output (textFormat
and spacing
parameters removed):
public static class BTreePrinter
{
class NodeInfo
{
public BNode Node;
public string Text;
public int StartPos;
public int Size { get { return Text.Length; } }
public int EndPos { get { return StartPos + Size; } set { StartPos = value - Size; } }
public NodeInfo Parent, Left, Right;
}
public static void Print(this BNode root, int topMargin = 2, int leftMargin = 2)
{
if (root == null) return;
int rootTop = Console.CursorTop + topMargin;
var last = new List();
var next = root;
for (int level = 0; next != null; level++)
{
var item = new NodeInfo { Node = next, Text = next.item.ToString(" 0 ") };
if (level < last.Count)
{
item.StartPos = last[level].EndPos + 1;
last[level] = item;
}
else
{
item.StartPos = leftMargin;
last.Add(item);
}
if (level > 0)
{
item.Parent = last[level - 1];
if (next == item.Parent.Node.left)
{
item.Parent.Left = item;
item.EndPos = Math.Max(item.EndPos, item.Parent.StartPos);
}
else
{
item.Parent.Right = item;
item.StartPos = Math.Max(item.StartPos, item.Parent.EndPos);
}
}
next = next.left ?? next.right;
for (; next == null; item = item.Parent)
{
Print(item, rootTop + 2 * level);
if (--level < 0) break;
if (item == item.Parent.Left)
{
item.Parent.StartPos = item.EndPos;
next = item.Parent.Node.right;
}
else
{
if (item.Parent.Left == null)
item.Parent.EndPos = item.StartPos;
else
item.Parent.StartPos += (item.StartPos - item.Parent.EndPos) / 2;
}
}
}
Console.SetCursorPosition(0, rootTop + 2 * last.Count - 1);
}
private static void Print(NodeInfo item, int top)
{
SwapColors();
Print(item.Text, top, item.StartPos);
SwapColors();
if (item.Left != null)
PrintLink(top + 1, "┌", "┘", item.Left.StartPos + item.Left.Size / 2, item.StartPos);
if (item.Right != null)
PrintLink(top + 1, "└", "┐", item.EndPos - 1, item.Right.StartPos + item.Right.Size / 2);
}
private static void PrintLink(int top, string start, string end, int startPos, int endPos)
{
Print(start, top, startPos);
Print("─", top, startPos + 1, endPos);
Print(end, top, endPos);
}
private static void Print(string s, int top, int left, int right = -1)
{
Console.SetCursorPosition(left, top);
if (right < 0) right = left + s.Length;
while (Console.CursorLeft < right) Console.Write(s);
}
private static void SwapColors()
{
var color = Console.ForegroundColor;
Console.ForegroundColor = Console.BackgroundColor;
Console.BackgroundColor = color;
}
}
and the result is: