I ended up with the following method, which allows you to print an 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<NodeInfo>(); 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 added some options that affect formatting. By default, it creates the most compact view.
To play with it, I changed the BTree class as follows:
public class BTree {
Using your sample data, here are a few 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, the adjusted algorithm for creating 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<NodeInfo>(); 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:
