C# Simple Binary Tree

Today I'm getting a bit into data structures. Recently I've found a need to store a large amount of records (16 million) in memory as a sort of cache. I tried using databases, etc, but it was all too slow. I needed super fast access times, well under a millisecond to speed up a large application with many data look ups. I tried a linked list but while the insert times were great the look ups again were too slow. I finally settled on a binary tree. Building the tree is slow, several minutes, but this data doesn't change and gives me super fast look up times, around .005 ms. To achieve this I wrote a very simple binary tree class.

It uses a node that is fit for my purposes. To make this class even better I would implement in generics with a node class that has to implement IComparable. This way you can make a binary tree for any type of class that can be compared. For now I'm hard coding the key/values for my specific needs. See the rather large code block below. I will add a zip file with the code as well.

using System;
using System.Collections;

namespace BinaryTree
{
   
    /// <summary>
    /// A very basic Binary Search Tree. Not generalized, stores
    /// name/value pairs in the tree nodes. name is the node key.
    /// The advantage of a binary tree is its fast insert and lookup
    /// characteristics. This version does not deal with tree balancing.
    /// </summary>
    public class TreeNode
    {
        #region Fields

        private string _name;
        
        private int[] _codes;
        
        public TreeNode Left, Right;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        /// <summary>
        /// Gets or sets the codes.
        /// </summary>
        /// <value>
        /// The codes.
        /// </value>
        public int[] Codes
        {
            get { return _codes; }
            set { _codes = value; }
        }
        
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="TreeNode"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="_codes">The _codes.</param>
        public TreeNode( string name, int[] codes )
        {
            _name = name;
            _codes = codes;
            Left = null;
            Right = null;
        }
    }

    /// <summary>
    /// A basic binary tree with a string, int[] node.  string is the key, int[] is the value
    /// </summary>
    public class BinaryTree
    {
        #region Fields

        // Points to the _root of the tree
        private TreeNode _root;

        // a count of nodes in the tree, duh!
        private int _count = 0;

        #endregion

        #region Properties

        /// <summary>
        /// Returns the number of nodes in the tree
        /// </summary>
        /// <returns>Number of nodes in the tree</returns>
        public int Count
        {
            get { return _count; }
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="BinaryTree"/> class.
        /// </summary>
        public BinaryTree()
        {
            _root = null;
            _count = 0;
        }

        #region Public Methods

        /// <summary>
        /// Clear the binary tree.
        /// </summary>
        public void Clear()
        {
            this.KillTree( ref _root );
            _count = 0;
        }       

        /// <summary>
        /// Find name in tree. Return a reference to the node
        /// if symbol found else return null to indicate failure.
        /// </summary>
        /// <param name="name">Name of node to locate</param>
        /// <returns>Returns null if it fails to find the node, else returns reference to node</returns>
        public TreeNode FindSymbol( string name )
        {
            TreeNode np = _root;

            int cmp;
            
            while( np != null )
            {
                cmp = String.CompareOrdinal( name, np.Name );

                if( cmp == 0 )   // found !
                {
                    return np;
                }

                if( cmp < 0 )
                {
                    np = np.Left;
                }
                else
                {
                    np = np.Right;
                }
            }
            return null;  // Return null to indicate failure to find name
        }

        /// <summary>
        /// Recursively locates an empty slot in the binary tree and inserts the node
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="tree">The tree.</param>
        private void Add( TreeNode node, ref TreeNode tree )
        {
            if( tree == null )
            {
                tree = node;
            }
            else
            {
                // If we find a node with the same name then it's 
                // a duplicate and we can't continue
                int comparison = String.CompareOrdinal( node.Name, tree.Name );

                if( comparison == 0 )
                {
                    throw new Exception();
                }

                if( comparison < 0 )
                {
                    Add( node, ref tree.Left );
                }
                else
                {
                    Add( node, ref tree.Right );
                }
            }
        }

        /// <summary>
        /// Add a symbol to the tree if it's a new one. Returns reference to the new
        /// node if a new node inserted, else returns null to indicate node already present.
        /// </summary>
        /// <param name="name">Name of node to add to tree</param>
        /// <param name="d">Value of node</param>
        /// <returns> Returns reference to the new node is the node was inserted.
        /// If a duplicate node (same name was located then returns null</returns>
        public TreeNode Insert( string name, int[] codes )
        {
            TreeNode node = new TreeNode( name, codes );
            try
            {
                if( _root == null )
                {
                    _root = node;
                }
                else
                {
                    Add( node, ref _root );
                }
                
                _count++;
                
                return node;

            }
            catch( Exception )
            {
                return null;
            }
        }

        // Searches for a node with name key, name. If found it returns a reference
        // to the node and to thenodes parent. Else returns null.
        private TreeNode FindParent( string name, ref TreeNode parent )
        {
            TreeNode np = _root;
            
            parent = null;
            
            int cmp;

            while( np != null )
            {
                cmp = String.Compare( name, np.Name );
                if( cmp == 0 )   // found !
                    return np;

                if( cmp < 0 )
                {
                    parent = np;
                    np = np.Left;
                }
                else
                {
                    parent = np;
                    np = np.Right;
                }
            }

            return null;  // Return null to indicate failure to find name

        }

        /// <summary>
        /// Find the next ordinal node starting at node startNode.
        /// Due to the structure of a binary search tree, the
        /// successor node is simply the left most node on the right branch.
        /// </summary>
        /// <param name="startNode">Name key to use for searching</param>
        /// <param name="parent">Returns the parent node if search successful</param>
        /// <returns>Returns a reference to the node if successful, else null</returns>
        public TreeNode FindSuccessor( TreeNode startNode, ref TreeNode parent )
        {
            parent = startNode;
            
            // Look for the left-most node on the right side
            startNode = startNode.Right;
            
            while( startNode.Left != null )
            {
                parent = startNode;
                startNode = startNode.Left;
            }

            return startNode;
        }

        /// <summary>
        /// Delete a given node. This is the more complex method in the binary search
        /// class. The method considers three senarios, 1) the deleted node has no
        /// children; 2) the deleted node as one child; 3) the deleted node has two
        /// children. Case one and two are relatively simple to handle, the only
        /// unusual considerations are when the node is the _root node. Case 3) is
        /// much more complicated. It requires the location of the successor node.
        /// The node to be deleted is then replaced by the sucessor node and the
        /// successor node itself deleted. Throws an exception if the method fails
        /// to locate the node for deletion.
        /// </summary>
        /// <param name="key">Name key of node to delete</param>
        public void Delete( string key )
        {
            TreeNode parent = null;

            // First find the node to delete and its parent
            TreeNode nodeToDelete = FindParent( key, ref parent );
            
            if( nodeToDelete == null )
            {
                throw new Exception( "Unable to delete node: " + key.ToString() );  // can't find node, then say so 
            }

            // Three cases to consider, leaf, one child, two children

            // If it is a simple leaf then just null what the parent is pointing to
            if( ( nodeToDelete.Left == null ) && ( nodeToDelete.Right == null ) )
            {
                if( parent == null )
                {
                    _root = null;
                    return;
                }

                // find out whether left or right is associated 
                // with the parent and null as appropriate
                if( parent.Left == nodeToDelete )
                {
                    parent.Left = null;
                }
                else
                {
                    parent.Right = null;
                }

                _count--;
                return;
            }

            // One of the children is null, in this case
            // delete the node and move child up
            if( nodeToDelete.Left == null )
            {
                // Special case if we're at the _root
                if( parent == null )
                {
                    _root = nodeToDelete.Right;
                    return;
                }

                // Identify the child and point the parent at the child
                if( parent.Left == nodeToDelete )
                {
                    parent.Right = nodeToDelete.Right;
                }
                else
                {
                    parent.Left = nodeToDelete.Right;
                }

                // Clean up the deleted node
                nodeToDelete = null; 
                _count--;
                return;

            }

            // One of the children is null, in this case
            // delete the node and move child up
            if( nodeToDelete.Right == null )
            {
                // Special case if we're at the _root            
                if( parent == null )
                {
                    _root = nodeToDelete.Left;
                    return;
                }

                // Identify the child and point the parent at the child
                if( parent.Left == nodeToDelete )
                {
                    parent.Left = nodeToDelete.Left;
                }
                else
                {
                    parent.Right = nodeToDelete.Left;
                }

                // Clean up the deleted node
                nodeToDelete = null; 
                _count--;
                return;

            }

            // Both children have nodes, therefore find the successor, 
            // replace deleted node with successor and remove successor
            // The parent argument becomes the parent of the successor
            TreeNode successor = FindSuccessor( nodeToDelete, ref parent );

            // Make a copy of the successor node
            TreeNode tmp = new TreeNode( successor.Name, successor.Codes );

            // Find out which side the successor parent is pointing to the
            // successor and remove the successor
            if( parent.Left == successor )
            {
                parent.Left = null;
            }
            else
            {
                parent.Right = null;
            }

            // Copy over the successor values to the deleted node position
            nodeToDelete.Name = tmp.Name;
            nodeToDelete.Codes = tmp.Codes;
            _count--;
        }

        /// <summary>
        /// Return the tree depicted as a simple string, useful for debugging, eg
        /// 50(40(30(20, 35), 45(44, 46)), 60)
        /// </summary>
        /// <returns>Returns the tree</returns>
        public string DrawTree()
        {
            return DrawNode( _root );
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.DrawTree();
        }

        #endregion

        #region Private Methods

        // Recursive destruction of binary search tree, called by method clear
        // and destroy. Can be used to kill a sub-tree of a larger tree.
        // This is a hanger on from its Delphi origins, it might be dispensable
        // given the garbage collection abilities of .NET
        private void KillTree( ref TreeNode p )
        {
            if( p != null )
            {
                KillTree( ref p.Left );

                KillTree( ref p.Right );

                p = null;

            }
        }

        /// <summary>
        /// Draws the node.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        private string DrawNode( TreeNode node )
        {
            if( node == null )
            {
                return "empty";
            }

            if( node.Left == null &&
                node.Right == null )
            {
                return node.Name;
            }
            if( node.Left != null &&
                node.Right == null )
            {
                return String.Format( "{0}({1}, )", node.Name, DrawNode( node.Left ) );
            }
            if( node.Right != null &&
                node.Left == null )
            {
                return String.Format( "{0}(_,{1})", node.Name, DrawNode( node.Right ) );

            }

            return String.Format( "{0}({1},{2})", node.Name, DrawNode( node.Left ), DrawNode( node.Right ) );

        }

        #endregion

    }
}

Comments

Popular posts from this blog

String.Replace vs Regex.Replace

C# Producer Consumer using Rabbit MQ

C# Form Application in Kiosk Mode/Fullscreen