How to avoid this stackoverflow exception? - c#

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.

Related

C# Increasing performance of a Linked List

I'm working on SPOJ problem where you have to write an algorithm that based on input string conditions outputs new string, but you can't exceede time limit.
problem link
The fastest i could get was by using two stacks, but time limit was still exceeded, now I tried implementing doubly linked list, but it's twice slower than when I used stack. Do you have any idea on how can I increase performance of implemented linked list, or maybe should I use other data structure for this problem? Thought of implementing Node as a structure but not really sure if you can do that.
using System;
namespace spoj
{
class LinkedList
{
private Node head;
private Node tail;
private int length;
public Node Head { get => head; }
public Node Tail { get => tail; }
public int Length { get => length; }
public LinkedList(Node head = null, Node tail = null, int length = 0)
{
this.head = head;
this.tail = tail;
this.length = length;
}
public void AddFirst(char value)
{
var addFirst = new Node(value);
addFirst.Next = head;
addFirst.Previous = null;
if (head != null)
head.Previous = addFirst;
head = addFirst;
length++;
}
public void Remove(Node node)
{
if (node.Previous == null)
{
head = node.Next;
head.Previous = null;
length--;
}
else if (node.Next == null)
{
tail = node.Previous;
tail.Next = null;
length--;
}
else
{
Node temp1 = node.Previous;
Node temp2 = node.Next;
temp1.Next = temp2;
temp2.Previous = temp1;
length--;
}
}
public void AddAfter(Node node, char input)
{
var newNode = new Node(input);
if (node.Next == null)
{
node.Next = newNode;
newNode.Previous = node;
length++;
}
else
{
Node temp1 = node;
Node temp2 = node.Next;
temp1.Next = newNode;
newNode.Previous = temp1;
newNode.Next = temp2;
temp2.Previous = newNode;
length++;
}
}
public string Print()
{
string temp = "";
if (head == null)
return temp;
for (int i = 0; i < length; i++)
{
temp += head.Value;
head = head.Next;
}
return temp;
}
}
class Node
{
private char value;
private Node next;
private Node previous;
public char Value { get => value; }
public Node Next { get => next; set { next = value; } }
public Node Previous { get => previous; set { previous = value; } }
public Node(char value)
{
this.value = value;
next = null;
previous = null;
}
}
class Program
{
static void Main(string[] args)
{
int testNum = Int32.Parse(Console.ReadLine());
for (int i = 0; i < testNum; i++)
{
var list = new LinkedList();
string input = Console.ReadLine();
var node = list.Head;
for (int j = 0; j < input.Length; j++)
{
if ((input[j] == '<' && node == null) | (input[j] == '>' && (node == null || node.Next == null)) | (input[j] == '-' && (node == null || node.Previous == null)))
continue;
else if (input[j] == '<')
{
node = node.Previous;
}
else if (input[j] == '>')
{
node = node.Next;
}
else if (input[j] == '-')
{
node = node.Previous;
list.Remove(node.Next);
}
else
{
if (node == null)
{
list.AddFirst(input[j]);
node = list.Head;
continue;
}
list.AddAfter(node, input[j]);
node = node.Next;
}
}
Console.WriteLine(list.Print());
}
}
}
}
An implementation using a linked list will not be as fast as one that uses StringBuilder, but assuming you are asking about a linked list based implementation I would suggest not to reimplement LinkedList. Just use the native one.
This means you don't have to change much in your code, just this:
Define the type of the list nodes as char: new LinkedList<char>();
Instead of .Head use .First
Instead of .Print use string.Join("", list)
However, there are these problems in your code:
When the input is >, you should allow the logic to execute when node is null. Currently you continue, but a null may mean that your "cursor" is in front of the non-empty list, so you should still deal with it, and move the "cursor" to list.First
When the input is -, you should still perform the removal even when node.Previous is null, because it is not the previous node that gets removed, but the current node. We should imagine the cursor to be between two consecutive nodes, and your removal logic shows that you took as rule that the cursor is between the current node and node.Next. You could also have taken another approach (with the cursor is just before node), but important is that all your logic is consistent with this choice.
When executing the logic for - -- in line with the previous point -- you should take into account that node.Previous could be null, and in that case you cannot do the removal as you have it. Instead, you could first assign the node reference to a temporary variable, then move the cursor, and then delete the node that is referenced by the temporary reference.
Here is the corrected code, using the native LinkedList implementation. I moved the logic for doing nothing (your continue) inside each separate case, as I find that easier to understand/debug:
using System;
using System.Collections.Generic;
public class Test
{
public static void Main()
{
int testNum = Int32.Parse(Console.ReadLine());
for (int i = 0; i < testNum; i++)
{
var list = new LinkedList<char>();
string input = Console.ReadLine();
var node = list.First;
for (int j = 0; j < input.Length; j++)
{
if (input[j] == '<')
{
if (node != null)
node = node.Previous;
}
else if (input[j] == '>')
{
if (node == null || node.Next != null)
node = node == null ? list.First : node.Next;
}
else if (input[j] == '-')
{
if (node != null) {
var temp = node;
node = node.Previous;
list.Remove(temp);
}
}
else
{
node = node == null ? list.AddFirst(input[j])
: list.AddAfter(node, input[j]);
}
}
Console.WriteLine(string.Join("", list));
}
}
}

SortedSet inserting element out of sort

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);
}

What 's the best solution to find an element in a deepest binary tree

Recently I had an interview question about finding an element in a binary tree. I coded both recursive and iterative solutions with C# but the problem was that in test cases when we have a tree with 1000000 nodes and all of them are on the left side. The interviewer said to me that my solutions (recursive and iterative) didn't save memory RAM enough for this case and I don't understand how to improve my solution.
// recusive Mode
public Node Find(int v)
{
if(v == value)
{
return this;
}else if(v <value){
if (left == null) return null;
return left.Find(v);
}else{
if (right == null) return null;
return right.Find(v);
}
}
// iterative
public Node Find(int v)
{
Node current = this;
while(value != v && current != null)
{
if (v < current.value)
{
if (current.left == null){ current = null};
else{current = current.left};
}
else
{
if (current.right == null) { current = null};
else{current = current.right };
}
}
return current;
}
Your iterative solution has some bugs in it.
// iterative
public Node Find(int v)
{
Node current = this;
// Here you need to compare current.value instead of just value
// Also, to use short-circuiting you need to put null-check first
// otherwise you might access current.value while current is null
while(current != null && current.value != v)
{
if (v < current.value)
{
//if (current.left == null){ current = null};
//else{current = current.left};
current = current.left; // the same as two commented out lines
}
else
{
//if (current.right == null) { current = null};
//else{current = current.right };
current = current.right; // the same as two commented out lines
}
}
return current;
}

A* Algorithm System.StackOverflowException

public List<Location2D> Path(Location2D start, Location2D goal)
{
openset = new List<NodeInfo>(); // The set of tentative nodes to be evaluated, initially containing the start node.
closedset = new List<NodeInfo>(); // The set of nodes already evaluated.
path = new List<Location2D>(); // The path result.
came_from = new Dictionary<Location2D, Location2D>();
NodeInfo Start = new NodeInfo();
Start.SetLoction(start.X, start.Y);
Start.H = GetHValue(start, goal);
openset.Add(Start);
while (openset.Count > 0) { // while openset is not empty
NodeInfo current = CheckBestNode(); //the node in openset having the lowest f_score[] value
if (current.Location.Equals(goal)) {
ReconstructPathRecursive(current.Location);
return path;
}
for (int i = 0; i < 8; i++) { // neighbor nodes.
NodeInfo neighbor = new NodeInfo();
neighbor.SetLoction((ushort)(current.Location.X + ArrayX[i]), (ushort)(current.Location.Y + ArrayY[i]));
bool tentative_is_better = false;
if (closedset.Contains(neighbor))
continue;
if (!map.Cells[neighbor.Location.X, neighbor.Location.Y].Walkable) { closedset.Add(neighbor); continue; }
Double tentative_g_score = current.G + DistanceBetween(current.Location, neighbor.Location);
if (!openset.Contains(neighbor)) {
openset.Add(neighbor);
neighbor.H = GetHValue(neighbor.Location, goal);
tentative_is_better = true;
} else if (tentative_g_score < neighbor.G) {
tentative_is_better = true;
} else {
tentative_is_better = false;
}
if (tentative_is_better) {
came_from[neighbor.Location] = current.Location;
neighbor.G = tentative_g_score;
}
}
}
return null;
}
private void ReconstructPathRecursive(Location2D current_node)
{
Location2D temp;
if (came_from.TryGetValue(current_node, out temp)) {
path.Add(temp);
ReconstructPathRecursive(temp);
} else {
path.Add(current_node);
}
}
so am Implementing A* Algorithm and when it find the Goal it goes to the ReconstructPathRecursive
and then the app crash and throw this exception An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
and This thread is stopped with only external code frames on the call stack. External code frames are typically from framework code but can also include other optimized modules which are loaded in the target process.
idk what's wrong!
It doesn't really have to be recursive, because it's tail recursive. So you can rewrite it like this:
private void ReconstructPathIterative(Location2D current_node)
{
Location2D temp;
while (came_from.TryGetValue(current_node, out temp))
{
path.Add(temp);
current_node = temp;
}
path.Add(current_node);
}
That only helps if the path was simply too long to fit on the stack, not if it was infinite.
I fixed it by adding NodeInfo Parent {get; set; } as a filed inside the NodeInfo class, and I add new List<NodeInfo> called Nodes when tentative is better :-
if (tentative_is_better) {
neighbor.Parent = current;
nodes.Add(neighbor);
neighbor.G = tentative_g_score;
}
then when it finds the goal :-
if (current.Location.Equals(goal)){
ReconstructPath(current);
path.Reverse();
return path;
}
where ReconstructPath :-
private void ReconstructPath(NodeInfo current) {
if (current.Parent == null) return;
path.Add(current.Parent.Location);
ReconstructPath(current.Parent);
}
and it works fine now.

Is this implementation of a Red Black Tree C# correct?

Please critique my code. I noticed my last assert fails with value 277. I expected the value to 255 (1/2 500+10). Is this a valid test or have I done something wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace RedBlackTree
{
public enum NodeColor
{
Red,
Black
}
public enum NodeSide
{
Left,
Right,
None
}
public class RedBlackTreeNode<T>
where T : IComparable<T>
{
public RedBlackTreeNode<T> Left { get; set; }
public RedBlackTreeNode<T> Right { get; set; }
public RedBlackTreeNode<T> Parent { get; set; }
public T Data {get; set;}
public NodeColor Color { get; set; }
public RedBlackTreeNode<T> Uncle
{
get
{
if (WhichSideAmIOn() == NodeSide.Left)
{
return this.Parent.Right;
}
else
return this.Parent.Left;
}
}
public string ToString()
{
return String.Format("Me: {0} Left: {1} Right: {2}", Data, Left != null ? Left.Data.ToString() : "null", Right != null ? Right.Data.ToString() : "null");
}
public NodeSide WhichSideAmIOn()
{
if (this.Parent == null) return NodeSide.None;
if (this.Parent.Left == this)
return NodeSide.Left;
if (this.Parent.Right == this)
return NodeSide.Right;
throw new Exception("Impossible - there can be only two sides. You must not be a child of your parent.");
}
}
public class RedBlackTree<T>
where T : IComparable<T>
{
private RedBlackTreeNode<T> Root { get; set; }
public void InsertNode(T data)
{
//make a node to hold the data - always red
RedBlackTreeNode<T> newNode = new RedBlackTreeNode<T>();
newNode.Data = data;
newNode.Color = NodeColor.Red;
//rule 1 - if the root is null, then hte new node is the root and roots can't be red.
if (Root == null)
{
Root = newNode;
Root.Color = NodeColor.Black;
return;
}
//otherwise if we're not the first node, insert by walking.
RedBlackTreeNode<T> walker = Root;
while (walker != null)
{
if (newNode.Data.CompareTo(walker.Data)< 0)
{
//walk left
if (walker.Left == null)
{
walker.Left = newNode;
newNode.Parent = walker;
break;
}
else
{
walker = walker.Left;
}
}
else if (newNode.Data.CompareTo(walker.Data) > 0)
{
//walk right
if (walker.Right == null)
{
walker.Right = newNode;
newNode.Parent = walker;
break;
}
else
{
walker = walker.Right;
}
}
else //todo: remove me
{
//we're equal, ignore this node in general
return;
}
}
//rebalance -
//at this point we have the parent , we have the newnode and we need to implement some rules.
Rebalance();
}
private void Rebalance()
{
RedBlackTreeNode<T> node = Root;
Stack<RedBlackTreeNode<T>> stack = new Stack<RedBlackTreeNode<T>>();
while (stack.Count !=0 || node !=null )
{
if (node != null)
{
stack.Push(node);
node = node.Left;
}
else
{
node = stack.Pop();
Rebalance(node);
node = node.Right;
}
}
}
private void Rebalance(RedBlackTreeNode<T> node)
{
if (node.Parent == null) return;
if (node.Parent.Color == NodeColor.Red) //rule 2 or 3
{
if (node.Uncle != null) //the rule 2 - change him to black as well
{
Rule2(node);
}
else //if my uncle doesn't exist, it's could be rule 3 or 4, which requires rotation
{
//if my parent and I are on the same side,
if (node.WhichSideAmIOn() == node.Parent.WhichSideAmIOn())
{
Rule3(node);
}
else
{
Rule4(node);
}
}
}
}
private void Rule2(RedBlackTreeNode<T> node)
{
//my parent + uncle needs to be black
if (node.Parent == null) throw new Exception("um how?");
node.Parent.Color = NodeColor.Black;
node.Uncle.Color = NodeColor.Black;
}
//The rule of two red nodes to the same side
//if the nodes of the tree are stacked in one direction and the two stacked nodes are red
//the middle node comes up to the parent and the top node becomes the left or right hand child.
private void Rule3(RedBlackTreeNode<T> node)
{
//make my grand parent, my parents left|right
//where am i?
NodeSide ns = node.WhichSideAmIOn();
if (node.Parent == null) throw new Exception("um how?");
RedBlackTreeNode<T> parent = node.Parent;
RedBlackTreeNode<T> grandParent = parent.Parent;
RedBlackTreeNode<T> greatGrandParent = grandParent.Parent;
//set my great, grand parent, to point at my parent
NodeSide gpSide = grandParent.WhichSideAmIOn();
if (gpSide == NodeSide.Left)
{
if (greatGrandParent !=null)
greatGrandParent.Left = parent;
}
else
{
if (greatGrandParent != null)
greatGrandParent.Right = parent;
}
//swap my grandparent into my parent's other child
if (ns == NodeSide.Left)
{
//set my parents right to my grandParent
parent.Right = grandParent;
grandParent.Left = null;
}
else if (ns == NodeSide.Right)
{
//set my parents right to my grandParent
parent.Left = grandParent;
grandParent.Right = null;
}
//reset the parent, update the root
parent.Parent = greatGrandParent;
if (greatGrandParent == null)
{
Root = parent;
}
grandParent.Parent = parent;
//swap colors
parent.Color = NodeColor.Black;
grandParent.Color = NodeColor.Red;
}
//The rule of two red nodes on different sides
//if the nodes of a tree are both red and one goes to the left, but the other goes to the right
//then the middle node becomes the parent and the top node becomes the left or right child
private void Rule4(RedBlackTreeNode<T> node)
{
if (node.Parent == null) throw new Exception("um how?");
RedBlackTreeNode<T> parent = node.Parent;
RedBlackTreeNode<T> grandParent = parent.Parent;
RedBlackTreeNode<T> greatGrandParent = grandParent.Parent;
//fix the reference that will be above me
NodeSide ns;
if (grandParent!= null)
{
ns = grandParent.WhichSideAmIOn();
//replace the reference to my grand parent with me
if (ns == NodeSide.Left)
{
greatGrandParent.Left = node;
}
else if (ns == NodeSide.Right)
{
greatGrandParent.Right = node;
}
}
//put my parent and my grand parent on the
//correct side of me.
ns = node.WhichSideAmIOn();
NodeSide parentSide = parent.WhichSideAmIOn();
if (ns == NodeSide.Left)
{
node.Left = grandParent;
node.Right = parent;
//I was the child of parent, wipe this refernce
parent.Left = null;
}
else
{
node.Left = parent;
node.Right = grandParent;
//i was the child of parent, wipe this reference
parent.Right = null;
}
parent.Parent = node;
grandParent.Parent = node;
//parent was the child of grandparent, wipe this reference
if (parentSide == NodeSide.Left) { grandParent.Left = null; }
if (parentSide == NodeSide.Right) { grandParent.Right = null; }
//reset my parent and root
node.Parent = greatGrandParent;
if (greatGrandParent == null)
{
Root = node;
}
//swap colors
node.Color = NodeColor.Black;
grandParent.Color = NodeColor.Red;
}
public void Print()
{
Stack<RedBlackTreeNode<T>> stack = new Stack<RedBlackTreeNode<T>>();
RedBlackTreeNode<T> temp = Root;
while (stack.Count != 0 || temp != null)
{
if (temp != null)
{
stack.Push(temp);
temp = temp.Left;
}
else
{
temp = stack.Pop();
Console.WriteLine(temp.Data.ToString());
temp = temp.Right;
}
}
}
public double Height
{
get
{
Stack<RedBlackTreeNode<T>> stack = new Stack<RedBlackTreeNode<T>>();
RedBlackTreeNode<T> temp = Root;
double currentHeight =0;
while (stack.Count != 0 || temp != null)
{
if (temp != null)
{
stack.Push(temp);
if (temp.Left != null || temp.Right != null)
{
currentHeight++;
}
temp = temp.Left;
}
else
{
temp = stack.Pop();
temp = temp.Right;
}
}
return currentHeight;
}
}
}
class Program
{
static void Main(string[] args)
{
RedBlackTree<int> rbt = new RedBlackTree<int>();
rbt.InsertNode(1);
rbt.InsertNode(2);
rbt.InsertNode(3);
rbt.InsertNode(4);
rbt.InsertNode(5);
rbt.InsertNode(6);
rbt.InsertNode(7);
rbt.InsertNode(8);
rbt.InsertNode(9);
rbt.InsertNode(10);
rbt.Print();
Assert.AreEqual(5, rbt.Height); //make sure sorted vals don't snake off to the left or right
//inert 500 more random numbers, height should remain balanced
Random random = new Random();
for (int i = 0; i < 500; i++)
{
rbt.InsertNode(random.Next(0, 10000));
}
Assert.AreEqual(255, rbt.Height);
}
}
}
I think your test is incorrect, although I think your code has other problems that the test isn't catching.
First of all, the Height property does not actually return the height, but the number of nodes with at least one child. If you want the height of the deepest node then you should do something like currentHeight = Math.Max(currentHeight, stack.Count) on each iteration instead. You may also want it to return an int rather than a double.
The number of nodes without children should be approximately half of them like you want, but red-black trees are not perfectly balanced. You can have a valid tree with one third of the nodes having one child, one third having two, and one third having none: start with a perfectly balanced tree with all black nodes at the last level and add a red child to each one. This maintains the red-black tree invariants, but as many as two-thirds of the nodes will have children.
Similarly, if you were to test depth it would be between log(N) and 2 log(N).
You may want to write tests that verify the invariants of the tree directly. Visit every node in the tree, and verify that every red node has a black parent and that every path to a leaf contains the same number of black nodes. If you run those tests after every insert in your test suite, you can be sure that the tree is always balanced.
As for the code itself, your Rebalance method crawls the entire tree on every insert. This means insert will require O(N) time and will negate the benefits of using a self-balancing tree. Retrieval will still be O(log N), but you could get the same result by keeping a sorted list and inserting elements into the appropriate place. You should only have to rebalance the tree along the path being inserted, which will only be O(log N) nodes.
I think some of your transformations are wrong. You don't check the color of the current node before calling Rule2, and that rule appears to change nodes to black without ensuring that other paths in the tree have the same number of black nodes. (I may be misreading it; red-black trees are too complicated to do entirely in my head.)
If you're looking for a reference implementation, the Wikipedia page on Red-black trees has an implementation in C that could easily be translated to C#, and SortedSet<T> is implemented using a red-black tree that you can view with Reflector.

Categories

Resources