I have a class with the following definition,
class BinomialNode
{
public int key; // The key value
public int x_point; // x co-ordinate for drawing
public int y_point; // y co-ordinate for drawing
public int degree; // number of siblings/children for current node
public BinomialNode parent;
public BinomialNode child;
public BinomialNode sibling;
...
}
We are learning Binomial Heaps in college and I've implemented the merge and insert algorithms in code. At least, when I pause Visual Studio and go over the "Locals" (by hovering the mouse over a variable), I see the data as I expect.
As an experiment, I've added 2 extra variables to the standard "Binomial Node". They are x_point and y_point. Now during program execution I see this,
Please note the area I've indicated above. It's supposed to represent the same node, but as we can see, the value of x_point is different. (In other cases, y_point is different)
Does anyone have a clue why this is happening? As I understand things, if it represents the same node, the data should be identical. But it isn't - which means it's not the same node. How can that be possible? If I ignore my "extra" x_point and y_point variables, the code runs perfectly! In fact, I wouldn't have even know this to be a problem.
It's not visible from my snippet, but x_point & y_point are the only values I EDIT outside the class definition. The others, while public are only read from.
EDIT:
Here is the code I've made,
class BinomialNode
{
public int key; // The key value
public int x_point; // x co-ordinate for drawing
public int y_point; // y co-ordinate for drawing
public int degree; // number of siblings/children for current node
public BinomialNode parent;
public BinomialNode child;
public BinomialNode sibling;
// Binomial Link takes the tree rooted at y and makes it a child of z
private static void Binomial_Link(ref BinomialNode y,ref BinomialNode z)
{
y.parent = z;
y.sibling = z.child;
z.child = y;
z.degree++;
}
// This merges the root lists of H1 and H2 into a single linked list that is sorted
// by degree in increasing order
private static BinomialNode Binomial_Heap_Merge(BinomialNode H1, BinomialNode H2)
{
BinomialNode H = new BinomialNode();
BinomialNode temp = H;
if (H1 == null) // if it's the first insert
{
return H2;
}
while (H1 != null && H2 != null)
{
if (H1.degree < H2.degree)
{
// insert H1 into position
temp.key = H1.key;
temp.x_point = H1.x_point;
temp.y_point = H1.y_point;
temp.child = H1.child;
temp.degree = H1.degree;
temp.sibling = new BinomialNode();
temp = temp.sibling;
// move H1 to the next sibling
H1 = H1.sibling;
}
else
{
// insert H2 into position
temp.key = H2.key;
temp.x_point = H2.x_point;
temp.y_point = H2.y_point;
temp.child = H2.child;
temp.degree = H2.degree;
temp.sibling = new BinomialNode();
temp = temp.sibling;
// move H2 to next sibling
H2 = H2.sibling;
}
}
// one of them hit null, so fill in the rest
while (H1 != null)
{
// insert H1 into position
temp.key = H1.key;
temp.x_point = H1.x_point;
temp.y_point = H1.y_point;
temp.child = H1.child;
temp.degree = H1.degree;
temp.sibling = new BinomialNode();
temp = temp.sibling;
// move H1 to the next sibling
H1 = H1.sibling;
}
while (H2 != null)
{
// insert H2 into position
temp.key = H2.key;
temp.x_point = H2.x_point;
temp.y_point = H2.y_point;
temp.child = H2.child;
temp.degree = H2.degree;
temp.sibling = new BinomialNode();
temp = temp.sibling;
// move H2 to the next sibling
H2 = H2.sibling;
}
// To remove the extra node added,
temp = H;
while (temp != null)
{
if (temp.sibling.key == 0 && temp.sibling.sibling == null && temp.sibling.child == null)
{
// found the extra, now to get rid of it!
temp.sibling = null;
}
temp = temp.sibling;
}
return H; // send back the merged heap
}
// Unites the binomial heaps H1 & H2 and returns resulting heap
public static BinomialNode Binomial_Heap_Union(BinomialNode H1, BinomialNode H2)
{
BinomialNode prev_x, x, next_x;
BinomialNode H = new BinomialNode();
H = Binomial_Heap_Merge(H1, H2);
// simple checks
if (H == null)
{
return H;
}
else
{
prev_x = null;
x = H;
next_x = x.sibling;
}
// now, for the actual merging
while (next_x != null)
{
if ((x.degree != next_x.degree) || (next_x.sibling != null && x.degree == next_x.sibling.degree))
{
prev_x = x;
x = next_x;
}
else if (x.key <= next_x.key)
{
x.sibling = next_x.sibling;
Binomial_Link(ref next_x, ref x);
}
else
{
if (prev_x == null)
{
H = next_x;
}
else
{
prev_x.sibling = x.sibling;
}
Binomial_Link(ref x, ref next_x);
x = next_x;
}
next_x = x.sibling;
}
// now, to return the merged heap
return H;
}
// inserting a key into a heap
public static void Binomial_Heap_Insert(ref BinomialNode H, int x)
{
BinomialNode H_temp = new BinomialNode();
H_temp.key = x;
H_temp.parent = null;
H_temp.degree = 0;
H_temp.sibling = null;
H_temp.child = null;
H = Binomial_Heap_Union(H, H_temp);
}
}
I use a form window to get the data from users to fill in the heap. The input is here,
private void button1_Click(object sender, EventArgs e)
{
BinomialNode.Binomial_Heap_Insert(ref B_HEAP1, Int32.Parse(numericUpDown1.Value.ToString()));
// drawing the heap
pictureBox1.Refresh();
int x = 0;
DrawNodeValue(pictureBox1, ref B_HEAP1, ref x, 0);
}
I hope the code isn't too badly made?
You're creating a new node, and then copying all the values over. Is that what you want to do? If you expect to be using the same nodes, then use the same nodes.
Instead of:
Node H = new Node();
Node temp = H;
if(node1 > node2)
temp.values = node1.values
else
temp.values = node2.values
Just use the actual objects...
Node temp;
if(node1 > node2)
temp = node1;
else
temp = node2;
I'm not sure where the values are getting separated, but this is why they're not actually the same node.
Related
I’ve written an implementation of A* that relies on sorting nodes by their F score in a sortedSet.
The sorting, in some cases, seems to insert a Node object at the 'Min' value when its compared 'F' value is actually the second lowest rather than the Min, as described. I'm completely baffled as to why this is happening. I believe it's causing the knock-on effect of causing nodeTree.Remove and nodeTree.RemoveWhere to fail, but that might be the actual cause of the issue, I'm honestly not sure - though I wouldn't know how to fix it if it is.
This is the comparer used. I assume it's relatively obvious that I'm not exactly sure how to implement these, but I think this should work as I intend.
public class FValueFirst : Comparer<PathfindingAgent.Node>
{
public override int Compare(PathfindingAgent.Node x, PathfindingAgent.Node y)
{
int result = x.F.CompareTo(y.F);
if (result == 0)
{
result = y.G.CompareTo(x.G);
}
if(x == y)
{
result = 0;
}
return result;
}
}
This is the Node object, for reference.
public class Node
{
public Cell cell;
public float G;
public float H;
public bool Opened;
public bool Closed;
public Node Previous;
public float F { get => G + H; }
}
This is the function it all occurs in. The result is deterministic, thankfully. Depending on the current destID and the particular layout of the grid's obstacles it will always get out of sort on the same iteration.
public void PathTo(Vector3Int destID)
{
SortedSet<Node> nodeTree = new SortedSet<Node>(new FValueFirst());
Vector3Int radius = PathfindingGrid.Instance.GridRadius;
NodeGrid = new Node[radius.x * 2 + 1, radius.y * 2 + 1, radius.z * 2 + 1];
Node startNode = new Node()
{
cell = PathfindingGrid.Cells[CurrentID.x, CurrentID.y, CurrentID.z],
G = 0,
H = 0
};
Node endNode = new Node()
{
cell = PathfindingGrid.Cells[destID.x, destID.y, destID.z],
G = 0,
H = 0
};
Vector3Int sID = startNode.cell.ID;
Vector3Int eID = endNode.cell.ID;
NodeGrid[sID.x, sID.y, sID.z] = startNode;
NodeGrid[eID.x, eID.y, eID.z] = endNode;
if (endNode.cell.IsOccupied) return;
nodeTree.Add(startNode);
int iterations = 0;
while(true)
{
Node node;
node = nodeTree.Min;
node.Closed = true;
nodeTree.RemoveWhere(n => n == node);
if(node == nodeTree.Min)
{
throw new Exception($"Incorrect node was removed from the tree");
}
if (node == endNode)
{
List<Node> chain = BacktraceChain(node);
Debug.Log($"Path found from {CurrentID} to {destID} with score {endNode.G} traversing {chain.Count} cells in {iterations} iterations");
DrawLine(chain, Color.white);
break;
}
List<Node> neighbours = GetNeighbours(node);
foreach(Node neighbour in neighbours)
{
if (neighbour == startNode || neighbour.Closed) continue;
float newg = Vector3Int.Distance(node.cell.ID, neighbour.cell.ID) + node.G;
if (!neighbour.Opened || newg < neighbour.G)
{
neighbour.G = newg;
neighbour.H = ManhattanHeuristic(neighbour, endNode);
neighbour.Previous = node;
if(!neighbour.Opened)
{
nodeTree.Add(neighbour);
neighbour.Opened = true;
}
else
{
nodeTree.RemoveWhere(n => n == neighbour);
nodeTree.Add(neighbour);
}
}
}
iterations++;
}
}
For posterity, I solved the issue - it was due to my inexperience with the SortedList type.
This code, found near the end of the function was to blame
if (!neighbour.Opened || newg < neighbour.G)
{
neighbour.G = newg;
neighbour.H = ManhattanHeuristic(neighbour, endNode);
neighbour.Previous = node;
if(!neighbour.Opened)
{
nodeTree.Add(neighbour);
neighbour.Opened = true;
}
else
{
nodeTree.RemoveWhere(n => n == neighbour);
nodeTree.Add(neighbour);
}
Specifically, an item in a tree cannot have its compared values modified to the point where it no longer compares correctly in that index. The item must first be removed from the list, modified, and readded.
My guess in hindsight is that, though removed immediately after modification, the tree is unable to be sufficiently traversed to access the target item due to the modification.
Thus my solution was to simply re-arrange the block so that the removal and addition occured on either side of the modification respectively, like so:
if (!neighbour.Opened || newg < neighbour.G)
{
if (neighbour.Opened)
{
if (!nodeTree.Remove(neighbour)) throw new Exception($"{neighbour} was not removed from tree");
}
else
{
neighbour.Opened = true;
}
neighbour.G = newg;
neighbour.H = ManhattanHeuristic(neighbour, endNode);
neighbour.Previous = node;
nodeTree.Add(neighbour);
}
THE PROBLEM
The problem I am running into is trying to get text to wrap to another line properly through a function. What I would like it to do is to wrap like it would in a word editor to the next line cleanly.
THE CODE
The code for the function and all other relevant information required:
// Internal variables that store values.
// Should NOT be called upon.
private int width;
private int height;
private int x;
private int y;
// Getter/Setter for window Width.
public int Width
{
get
{
width = Console.WindowWidth;
return width;
}
private set
{
if (value <= 0)
{
throw new Exception("Width setter: Invalid Width inputted");
}
else
{
width = value;
}
}
}
// Getter/Setter for window Height.
public int Height
{
get
{
height = Console.WindowHeight;
return height;
}
private set
{
if (value <= 0)
{
throw new Exception("Height setter: Invalid height inputted");
}
else
{
height = value;
}
}
}
// Getter/Setter for cursor X position.
public int X
{
get
{
x = Console.CursorLeft;
return x;
}
private set
{
if ( value < 0 || value > Width )
{
throw new Exception("X Setter: Invalid X position.");
}
else
{
x = value;
}
}
}
// Getter/Setter for cursor Y position.
public int Y
{
get
{
y = Console.CursorTop;
return y;
}
private set
{
if (value < 0 || value > Height)
{
throw new Exception("Y Setter: Invalid Y position.");
}
else
{
y = value;
}
}
}
// SetCursorPosition is a method to, well, change the cursor position.
public void SetCursorPosition(int newX, int newY)
{
this.X = newX;
this.Y = newY;
Console.SetCursorPosition(newX, newY);
}
// WriteLine writes a line to the console.
// It also sanity checks the length of the string doesn't exceed the width
// of the window, and changes the string to be among two or even three lines
// if needed.
public void WriteLine( int yPos, string String )
{
int stringLength = String.Length;
if (stringLength > Width)
{
string[] textToSplit = String.Split();
StringBuilder splitText = new StringBuilder();
int currentLineLength = 0;
for ( int i = 0; i < textToSplit.Length; i++ )
{
if ( currentLineLength > Width )
{
if ( textToSplit[i].Length > Width - currentLineLength )
{
splitText.Append("\n");
splitText.Append(textToSplit[i]);
currentLineLength = 0;
}
else
{
splitText.Append(textToSplit[i]);
splitText.Append("\n");
currentLineLength = 0;
}
}
else
{
splitText.Append(textToSplit[i]);
splitText.Append(" ");
currentLineLength = currentLineLength + textToSplit[i].Length + 1;
}
}
Console.Write(splitText);
}
// The string fits on one line, so just print it out.
else
{
SetCursorPosition(0, yPos);
this.Y = yPos;
Console.Write(String);
}
}
THE RESULTS
What I expect the text to look like after inputting it is:
This is a really long string intended to demonstrate how words should wrap to the next line of the console. If I extending the string longer, it should wrap.
What it actually looks like:
You can use the following: to record a line in your view, example:
var new text = "text"
var cmd = #"what wish
show {change}";
If you want to change or collate something in this text you can use it that way.
cmd = cmd.Replace ("{change}", new text);
If the item you want to change is a list of objects you can use:
List <int> numbers = new List <int> (new int [] {2, 3, 5});
cmd = cmd.Replace ("{change}", string.Join (",", numbers));
I'm trying to write a function in C# that allows me to swap two nodes of a binary tree but it does not work as expected.
Here is the class with the swap method:
class Node
{
public int value;
public Node parent { get; set; }
public Node left { get; set; }
public Node right { get; set; }
public Node addLeft(int value)
{
this.left = new Node(value);
this.left.parent = this;
this.left.left = null;
this.left.right = null;
return this.left;
}
public Node addRight(int value)
{
this.right = new Node(value);
this.right.parent = this;
this.right.left = null;
this.right.right = null;
return this.right;
}
public Node(int value)
{
this.value = value;
this.parent = null;
this.left = null;
this.right = null;
}
public Node getRoot()
{
Node n = this;
while(n.parent!=null)
{
n = n.parent;
}
return n;
}
public static void swap(ref Node n1,ref Node n2)
{
//updating references of n1 and n2 parents
if(n1.Equals(n1.parent.left)) //n1 is a left child
{
n1.parent.left = n2;
}
else if(n1.Equals(n1.parent.right)) //n1 is a right child
{
n1.parent.right = n2;
}
else
{
throw new Exception("Something is wrong");
}
if (n2.Equals(n2.parent.left)) //n2 is a left child
{
n2.parent.left = n1;
}
else if (n2.Equals(n2.parent.right)) //n2 is a right child
{
n2.parent.right = n1;
}
else
{
throw new Exception("Something is wrong");
}
//updating references of n1 and n2 childs
if(n1.left != null)
{
n1.left.parent = n2;
}
if (n1.right != null)
{
n1.right.parent = n2;
}
if (n2.left != null)
{
n2.left.parent = n1;
}
if (n2.right != null)
{
n2.right.parent = n1;
}
//Swapping n1 and n2 references
Node t_p = n1.parent;
Node t_l = n1.left;
Node t_r = n1.right;
n1.parent = n2.parent;
n1.left = n2.left;
n1.right = n2.right;
n2.parent = t_p;
n2.left = t_l;
n2.right = t_r;
}
}
And here is my main function:
static void Main(string[] args)
{
Node root = new Node(10);
Node a = root.addLeft(1);
Node b = root.addRight(2);
Node c = a.addLeft(3);
Node d = a.addRight(4);
Node e = b.addLeft(5);
Node f = b.addRight(6);
Node g = d.addLeft(7);
Node h = d.addRight(8);
Node.swap(ref a,ref d);
Console.WriteLine("Value is: " + root.left.value);
Console.WriteLine("Value is: " + root.left.right.value);
Console.WriteLine("Root: " + a.getRoot().value);
Console.WriteLine("Root: " + d.getRoot().value);
Console.Read();
}
The output of the code above is:
Value is: 4
Value is: 1
It hangs after the second Console.WriteLine and I don't understand why. Can you please tell me what am I doing wrong?
EDIT:
And if I try to swap the nodes multiple times, the "Something is wrong" exception is thrown.
while (n.parent != null)
This condition is never met so you're stuck in a while loop.
Your swap method creates a node with an infinite ancestor tree (parent). If you walk up the current node n in .getRoot() you never find a null parent.
Here is the state of the tree before we begin swapping
((Root(10))
/ \
a(1) b(2)
/ \ / \
c(3) d(4) e(5) f(6)
/ \
g(7) h(8)
If you swap only the child nodes for a & d, you end up with an circular reference for parent.
Something more like this for your swap method should work. I left this verbose for clarity's sake.
public static void swap(ref Node A, ref Node B)
{
var newA = new Node(B.value);
var newB = new Node(A.value);
newA.left = A.left;
newA.right = A.right;
newA.parent = A.parent;
newB.left = B.left;
newB.right = B.right;
newB.parent = B.parent;
// Fix up parent node for A
if (A.parent.left == A)
{
// A is a left node
A.parent.left = newA;
}
if (A.parent.right == A)
{
// A is a Right node
A.parent.right = newA;
}
// Fix up parent node for B
if (B.parent.left == B)
{
// B is a left node
B.parent.left = newB;
}
if (B.parent.right == B)
{
// B is a right node
B.parent.right = newB;
}
if (newA.right == B)
{
// If B was a right child of A, update reference to newB
newA.right = newB;
}
if (newA.left == A)
{
// If B was a left child of A, update reference to newB
newA.left = newB;
}
if (newB.right == A)
{
// If A was a right child of B, update reference to newA
newB.right = newA;
}
if (newB.left == A)
{
// If A was a left child of B, update reference to newA
newA.left = newB;
}
// Update child references to be orphaned to point to new parents for A
A.left.parent = newA;
A.right.parent = newA;
// Update child references to be orphaned to point to new parents for A
B.left.parent = newB;
B.right.parent = newB;
// Final Swap to update ref types
A = newA;
B = newB;
}
Desired State after the swap
((Root(10))
/ \
d(4) b(2)
/ \ / \
c(3) a(1) e(5) f(6)
/ \
g(7) h(8)
Here is some quick & dirty verification code that runs in the console. I haven't checked all possible cases, but it seems to update all the relevant nodes in this case now.
static void Main(string[] args)
{
var root = new Node(10);
var a = root.addLeft(1);
var b = root.addRight(2);
var c = a.addLeft(3);
var d = a.addRight(4);
var e = b.addLeft(5);
var f = b.addRight(6);
var g = d.addLeft(7);
var h = d.addRight(8);
Node.swap(ref a, ref d);
if (root.left.value != 4)
throw new ApplicationException("New Root --> left --> value != 4 as expected");
Console.WriteLine("New root --> left node has correct value of 4");
if ((root.left.right.parent != root.left))
throw new Exception("and root --> left --> right has incorrect parent");
Console.WriteLine("Root --> left --> right has the correct parent");
if (root.left.right.value != 1)
throw new ApplicationException("New Root --> left --> right --> value did not equal 1.");
Console.WriteLine("New Root --> Left --> right has the correct value of 1");
if (root.left.right.left.value != 7)
throw new ApplicationException("New Root --> left --> right --> left --> value was not 7 as expected.");
Console.WriteLine("New Root --> left --> right --> left.value had a value of 7 as expected");
if (root.left.right.left.parent != root.left.right)
throw new ApplicationException("New Root --> left --> right --> left --> parent was not root --> left --> right as expected");
Console.WriteLine("New Root --> Left --> right --> left has the correct value of 7 and expected parent");
Console.Read();
}
Please take a look at this line:
if (n1.right != null)
{
n1.right.parent = n2;
}
If n2 is the right child of the n1, as in this case, n1.rightisn2, so n1.right.parent = n2 is actually n2.parent = n2 which causes the loop.
To solve the problem you have to either make copies of the nodes and then replace them, or try to do both swaps atomically and independently.
If I am understanding what your swap function should be doing, it should be as simple as this:
public static void swap(Node n1, Node n2)
{
Node left1 = n1.left;
Node left2 = n2.left;
Node right1 = n1.right;
Node right2 = n2.right;
Node parent1 = n1.parent;
Node parent2 = n2.parent;
n1.left = left2;
n2.left = left1;
n1.right = right2;
n2.right = right1;
n1.parent = parent2;
n2.parent = parent1;
}
What I think you need to do is simple swap n1's left and right with n2's left and right. No need for ref's either, since its a reference type and not a value type.
I have a little "project" which involves drawing Symmetric Binary B-Trees, like this one:
But I cant figure out a way to correctly calculate the position (x,y) of each node. The way Im doing it right now, as the tree's height grows some nodes tend to get overlapped by others.
Can anyone give me some light as to how can I calculate the position of a node?
Im using C# and this is the class I have right now that represents a node:
class SBBTreeNode<T> where T : IComparable {
public SBBTreeNode(T item) {
Data = item;
Left = null;
Right = null;
}
public T Data { get; private set; }
public SBBTreeNode<T> Left;
public SBBTreeNode<T> Right;
public bool IsHorizontal { get; set; } //Is this node horizontal?
public bool IsLeaf() {
return Left == null && Right == null;
}
}
Here is a drawing routine:
void drawTree(Graphics G)
{
if (flatTree.Count <= 0) return;
if (maxItemsPerRow <= 0) return;
if (maxLevels <= 0) return;
int width = (int)G.VisibleClipBounds.Width / (maxItemsPerRow + 2);
int height = (int)G.VisibleClipBounds.Height / (maxLevels + 2);
int side = width / 4;
int textOffsetX = 3;
int textOffsetY = 5;
int graphOffsetY = 50;
Size squaresize = new Size(side * 2, side * 2);
foreach (SBBTreeNode<string> node in flatTree)
{
Point P0 = new Point(node.Col * width, node.Row * height + graphOffsetY);
Point textPt = new Point(node.Col * width + textOffsetX,
node.Row * height + textOffsetY + graphOffsetY);
Point midPt = new Point(node.Col * width + side,
node.Row * height + side + graphOffsetY);
if (node.Left != null)
G.DrawLine(Pens.Black, midPt,
new Point(node.Left.Col * width + side,
node.Left.Row * height + side + graphOffsetY));
if (node.Right != null)
G.DrawLine(Pens.Black, midPt,
new Point(node.Right.Col * width + side,
node.Right.Row * height + side + graphOffsetY));
G.FillEllipse(Brushes.Beige, new Rectangle(P0, squaresize));
G.DrawString(node.Data, Font, Brushes.Black, textPt);
G.DrawEllipse(Pens.Black, new Rectangle(P0, squaresize));
}
}
and its result:
Usage:
flatTree = FlatTree();
setRows();
setCols();
panel_tree.Invalidate();
Now for the various pieces that lead up to this:
The drawTree routine is obviously triggered from a Panel's Paint event.
I uses a few class level variables:
This is the Tree I build in my tests; please note that to make things a little simpler I have dumped your generic type T for string:
Dictionary<string, SBBTreeNode<string> > tree
= new Dictionary<string, SBBTreeNode<string>>();
This is a flat traversal copy of the tree, that is, its elements are ordered by level and from left to right:
List<SBBTreeNode<string>> flatTree = new List<SBBTreeNode<string>>() ;
Here are the dimesions of the tree:
int maxItemsPerRow = 0;
int maxLevels = 0;
This is how the flat tree is created, using a Queue:
List<SBBTreeNode<string>> FlatTree()
{
List<SBBTreeNode<string>> flatTree = new List<SBBTreeNode<string>>();
Queue<SBBTreeNode<string>> queue = new Queue<SBBTreeNode<string>>();
queue.Enqueue((SBBTreeNode<string>)(tree[tree.Keys.First()]));
flatNode(queue, flatTree);
return flatTree;
}
This is the recursive call to get the nodes in order:
void flatNode(Queue<SBBTreeNode<string>> queue, List<SBBTreeNode<string>>flatTree)
{
if (queue.Count == 0) return;
SBBTreeNode<string> node = queue.Dequeue();
if (!node.IsHorizontal) flatTree.Add(node);
if (node.Left != null) { queue.Enqueue(node.Left); }
if (node.Left != null && node.Left.Right != null && node.Left.Right.IsHorizontal)
queue.Enqueue(node.Left.Right);
if (node.Right != null)
{
if (node.Right.IsHorizontal) flatTree.Add(node.Right);
else queue.Enqueue(node.Right);
}
flatNode(queue, flatTree);
}
Finally we can set the (virtual) coordinates of each node:
void setCols()
{
List<SBBTreeNode<string>> FT = flatTree;
int levelMax = FT.Last().Row;
int LMaxCount = FT.Count(n => n.Row == levelMax);
int LMaxCount1 = FT.Count(n => n.Row == levelMax-1);
if (LMaxCount1 > LMaxCount)
{ LMaxCount = LMaxCount1; levelMax = levelMax - 1; }
int c = 1;
foreach (SBBTreeNode<string> node in FT) if (node.Row == levelMax)
{
node.Col = ++c;
if (node.Left != null) node.Left.Col = c - 1;
if (node.Right != null) node.Right.Col = c + 1;
}
List<SBBTreeNode<string>> Exceptions = new List<SBBTreeNode<string>>();
for (int n = FT.Count- 1; n >= 0; n--)
{
SBBTreeNode<string> node = FT[n];
if (node.Row < levelMax)
{
if (node.IsHorizontal) node.Col = node.Left.Col + 1;
else if ((node.Left == null) | (node.Right == null)) {Exceptions.Add(node);}
else node.Col = (node.Left.Col + node.Right.Col) / 2;
}
}
// partially filled nodes will need extra attention
foreach (SBBTreeNode<string> node in Exceptions)
textBox1.Text += "\r\n >>>" + node.Data;
maxLevels = levelMax;
maxItemsPerRow = LMaxCount;
}
Note that I have not coded the special case of partially filled nodes but only add them to a list of exceptions; you have to decide what to do with those, ie if they can happen and where they ought to be painted.
OK, this is almost it. We have to do two more things:
I have taken the liberty to add two coordinate fields to your node class:
public int Row { get; set; }
public int Col { get; set; }
And I have writen my AddNode routine in such a way that the level of each node is set right there.
You will certainly want/need to do it differently. A simple SetRows routine is a snap, especially when you use the flatTree for transversal:
void setRows()
{
foreach (SBBTreeNode<string> node in flatTree)
{
if (node.Left != null) node.Left.Row = node.Row + 1;
if (node.Right != null) node.Right.Row =
node.Row + 1 - (node.Right.IsHorizontal ? 1:0);
}
}
Explanation:
Besides the flatTree, I use for drawing, the core of the solution is the SetCols routine.
In a balanced B-Tree the maximum width is reached either at the last or the second-to-last row.
Here I count the number of nodes in that row. This gives me the width of the whole tree, maxItemsPerRow. The routine also sets the height as maxLevels
Now I first set the Col values in that widest row, from left to right (and if present to dangling children in the last row.)
Then I move up level by level and calculate each Col value as the middle between the Left and Right Child, always watching out for horizontal nodes.
Note that I assume that all horizontal nodes are right children! If that is not true you will have to make various adaptions in both the FlatTree and the setCol routines..
I would start by placing the root node at (0,0) (really doesn't matter where you start). Call this point (parent_X, parent_Y). Then pick a starting width (say 2^(number of levels in your tree), if you know how many levels your tree has, otherwise, just pick any width).
The left child goes at position (parent_X-width/2, parent_Y-1) and the right child goes at position (parent_X+width/2, parent_Y-1). Then change the width to width = width/2. If a child happens to be horizontal, you can just forget the parent_Y-1 part and keep the parent_Y. Then just repeat on each of the children of the head node. Each time you move down a level, replace width with width/2 - epsilon.
Hope this helps.
Here is the situation, I am developing a binary search tree and in each node of the tree I intend to store the height of its own for further balancing the tree during avl tree formation. Previously I had an iterative approach to calculate the height of a node during balancing the tree like the following.
(The following code belongs to a class called AVLTree<T> which is a child class of BinarySearchTree<T>)
protected virtual int GetBalance(BinaryTreeNode<T> node)
{
if(node != null)
{
IEnumerable<BinaryTreeNode<T>> leftSubtree = null, righSubtree = null;
if (node.Left != null)
leftSubtree = node.Left.ToEnumerable(BinaryTreeTraversalType.InOrder);
if (node.Right != null)
righSubtree = node.Right.ToEnumerable(BinaryTreeTraversalType.InOrder);
var leftHeight = leftSubtree.IsNullOrEmpty() ? 0 : leftSubtree.Max(x => x.Depth) - node.Depth;
var righHeight = righSubtree.IsNullOrEmpty() ? 0 : righSubtree.Max(x => x.Depth) - node.Depth;
return righHeight - leftHeight;
}
return 0;
}
But it was incurring a lot of performance overhead.
Performance of an AVL Tree in C#
So I went for storing the height value in each node at the time of insertion in the BinarySearchTree<T>. Now during balancing I am able to avoid this iteration and I am gaining the desired performance in AVLTree<T>.
But now the problem is if I try to insert a large number of data say 1-50000 sequentially in BinarySearchTree<T> (without balancing it), I am getting StackoverflowException. I am providing the code which is causing it. Can you please help me to find a solution which will avoid this exception and also not compromise with the performance in its child class AVLTree<T>?
public class BinaryTreeNode<T>
{
private BinaryTreeNode<T> _left, _right;
private int _height;
public T Value {get; set; }
public BinaryTreeNode<T> Parent;
public int Depth {get; set; }
public BinaryTreeNode()
{}
public BinaryTreeNode(T data)
{
Value = data;
}
public BinaryTreeNode<T> Left
{
get { return _left; }
set
{
_left = value;
if (_left != null)
{
_left.Depth = Depth + 1;
_left.Parent = this;
}
UpdateHeight();
}
}
public BinaryTreeNode<T> Right
{
get { return _right; }
set
{
_right = value;
if (_right != null)
{
_right.Depth = Depth + 1;
_right.Parent = this;
}
UpdateHeight();
}
}
public int Height
{
get { return _height; }
protected internal set
{
_height = value;
if (Parent != null) {
Parent.UpdateHeight();
}
}
}
private void UpdateHeight()
{
if (Left == null && Right == null) {
return;
}
if(Left != null && Right != null)
{
if (Left.Height > Right.Height)
Height = Left.Height + 1;
else
Height = Right.Height + 1;
}
else if(Left == null)
Height = Right.Height + 1;
else
Height = Left.Height + 1;
}
}
public class BinarySearchTree<T>
{
private readonly Comparer<T> _comparer = Comparer<T>.Default;
public BinarySearchTree()
{
}
public BinaryTreeNode<T> Root {get; set;}
public virtual void Add(T value)
{
var n = new BinaryTreeNode<T>(value);
int result;
BinaryTreeNode<T> current = Root, parent = null;
while (current != null)
{
result = _comparer.Compare(current.Value, value);
if (result == 0)
{
parent = current;
current = current.Left;
}
if (result > 0)
{
parent = current;
current = current.Left;
}
else if (result < 0)
{
parent = current;
current = current.Right;
}
}
if (parent == null)
Root = n;
else
{
result = _comparer.Compare(parent.Value, value);
if (result > 0)
parent.Left = n;
else
parent.Right = n;
}
}
}
I am getting the StackoverflowException in calculating the height at the following line
if (Parent != null) {
Parent.UpdateHeight();
}
in the Height property of BinaryTreeNode<T> class. If possible please suggest me some work around.
BTW, thanks a lot for your attention to read such a long question :)
When you add a node you compute the height by iterating over all the parent nodes recursively. A .NET process has limited stack space and given a big tree you will consume all stack space and get a StackOverflowException. You can change the recursion into an iteration to avoid consuming stack space. Other languages like functional languages are able to recurse without consuming stack space by using a technique called tail recursion. However, in C# you will have to manually modify your code.
Here are modified versions of Height and UpdateHeight in BinaryTreeNode<T> that doesn't use recursion:
public int Height {
get { return _height; }
private set { _height = value; }
}
void UpdateHeight() {
var leftHeight = Left != null ? Left.Height + 1 : 0;
var rightHeight = Right != null ? Right.Height + 1 : 0;
var height = Math.Max(leftHeight, rightHeight);
var node = this;
while (node != null) {
node.Height = height;
height += 1;
node = node.Parent;
}
}
You can add a tail. call in il, decompile the file and then compile it again.
Example:
.... IL_0002: add
tail.
IL_0003: call ...
IL_0008: ret
Example on compiling it again:
ilasm C:\test.il /out=C:\TestTail.exe
(this is probably not what you want, but again it's just an example)
I'm sure you can figure it out and make it work, it's not to hard.
The big downside is that recompilation will get rid of your tail call so I recommend to set up a build task in msbuild to do it automatically for you.
I think I found the solution, I modified the code as follows and it worked like a charm
public int Height
{
get { return _height; }
protected internal set
{
_height = value;
}
}
private void UpdateHeight()
{
if (Left == null && Right == null) {
return;
}
if(Left != null && Right != null)
{
if (Left.Height > Right.Height)
Height = Left.Height + 1;
else
Height = Right.Height + 1;
}
else if(Left == null)
Height = Right.Height + 1;
else
Height = Left.Height + 1;
var parent = Parent;
while (parent != null) {
parent.Height++;
parent = parent.Parent;
}
}
Thanks a lot guys who spend some time for me to tried to find out the solution.
If you are inserting large amounts of data in one go I would think you'd be better of batch inserting the data without the call to Parent.UpdateHeight then walk the tree setting the height as you go.
Adding future nodes I would walk the tree, starting at the root, incrementing the height as you go.