简体   繁体   中英

Find value in binary search tree for arbitrary data type

I have a binary tree written in c# with nodes defined with IComparable data. It all works just fine in the sense that I can populate the tree with any data type I like (although I've only tried populating it with a single type of data at a time) and perform functions like finding the depth, in-order searching, counting leaves, etc.

I'm trying to write a function to find a data value in the tree using a recursive algorithm. The algorithm finds the data, but then does not stop when the data is found. There is one exception -- when the data is in the root node. I can't figure out where it is going wrong. The function reports that it found the data, at which point it is supposed to return that node and quit, but it keeps going looking in the children nodes.

Thanks to the accepted answer, here is the working code I have:

    private TreeNode findValueStartingAtNode(TreeNode node, IComparable value)
    {
        if (node == null)
        {
            return null;
        }

        int test = value.CompareTo(node.data);

        if (test < 0)
        {
            return findValueStartingAtNode(node.left_child, value);
        }
        else if (test > 0)
        {
            return findValueStartingAtNode(node.right_child, value);
        }
        else
        {
            return node;
        }
    }

Code:

    public TreeNode findValue(IComparable value)
    {
        TreeNode node = findValueStartingAtNode(this.root, value);
        if (node == null)
        {
            Console.WriteLine("value not found");
            return null;
        }
        else
        {
            return findValueStartingAtNode(this.root, value);
        }
    }

    private TreeNode findValueStartingAtNode(TreeNode node, IComparable value)
    {
        Console.WriteLine("looking for value {0}", value);

        if (node == null)
        {
            Console.WriteLine("node is null -- returning null");
            return null;
        }
        else if (value.CompareTo(node.data) == 0)
        {
            Console.WriteLine("value found at current node");
            Console.WriteLine("current node data is {0}", node.data);
            Console.WriteLine("done and returning node");
            return node;
        }
        else
        {
            Console.WriteLine("checking children");
            TreeNode left = findValueStartingAtNode(node.left_child, value);
            TreeNode right = findValueStartingAtNode(node.right_child, value);

            Console.WriteLine("the values are left: {0}, right: {1}", left.data, right.data);

            if (value.CompareTo(left.data) == 0)
            {
                Console.WriteLine("value found in left child");
                return left;
            }
            else if (value.CompareTo(right.data) == 0)
            {
                Console.WriteLine("value found in right child");
                return right;
            }
            else
            {
                Console.WriteLine("value not found in either child");
                Console.WriteLine("current node data is {0}", node.data);
                return null;
            }
        }
    }

Output:

C:\Users\abalter\Documents\CS273\TreeNode\TreeNode\bin\Debug>TreeNode.exe
looking for value 50
value found at current node
current node data is 50
done and returning node
looking for value 50
value found at current node
current node data is 50
done and returning node
(in main) value 50 found


looking for value 45
checking children
looking for value 45
value found at current node
current node data is 45
done and returning node
looking for value 45
checking children
looking for value 45
checking children
looking for value 45
node is null -- returning null
looking for value 45
checking children
looking for value 45
node is null -- returning null
looking for value 45
node is null -- returning null

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at TreeNode.BinaryTree.findValueStartingAtNode(TreeNode node, IComparable value) in c:\Users\abalter\Documents\CS273\TreeNode\TreeNode\BinaryTree.cs:line 220
   at TreeNode.BinaryTree.findValueStartingAtNode(TreeNode node, IComparable value) in c:\Users\abalter\Documents\CS273\TreeNode\TreeNode\BinaryTree.cs:line 218
   at TreeNode.BinaryTree.findValueStartingAtNode(TreeNode node, IComparable value) in c:\Users\abalter\Documents\CS273\TreeNode\TreeNode\BinaryTree.cs:line 217
   at TreeNode.BinaryTree.findValueStartingAtNode(TreeNode node, IComparable value) in c:\Users\abalter\Documents\CS273\TreeNode\TreeNode\BinaryTree.cs:line 218
   at TreeNode.BinaryTree.findValue(IComparable value) in c:\Users\abalter\Documents\CS273\TreeNode\TreeNode\BinaryTree.cs:line 186
   at TreeNode.Program.Main(String[] args) in c:\Users\abalter\Documents\CS273\TreeNode\TreeNode\Program.cs:line 36

C:\Users\abalter\Documents\CS273\TreeNode\TreeNode\bin\Debug>

A Binary Search Tree has certain properties. The property that's important here is that for each node, everything in that node's left subtree is smaller than the node's value and everything in its right subtree is larger (according to the comparer).

You only ever have to look in one subtree. Use that property to find the item you're looking for:

private TreeNode findValueStartingAtNode(TreeNode node, IComparable value)
{
    if (node == null) return null;

    int comp = value.CompareTo(node.data);

    if (comp == 0) return node; //Found it
    if (comp < 0) return findValueStartingAtNode(node.left_child, value);  //The item must be in the left subtree
    return findValueStartingAtNode(node.right_child, value); // The item must be in the right subtree
}

Bonus: instead of a recursive method, here's an iterative Search function from my own implementation: (It's also generic)

private BinaryTreeNode<T> Search(BinaryTreeNode<T> node, T item)
{
    if (node == null) return null;
    int c;
    while (node != null && (c = comparer.Compare(item, node.Value)) != 0)
        node = c < 0 ? node.Left : node.Right;
    return node;
}

Instead of looking down the left and right, then checking to see if it was found you should look down the left only. If it is found there, return that, otherwise look down the right. If it is there, return that, otherwise return null. Your else would look something like this:

Console.WriteLine("checking children");
TreeNode left = findValueStartingAtNode(node.left_child, value);
if (left != null && value.CompareTo(left.data) == 0)
{
    Console.WriteLine("value found in left child");
    return left;
}
else 
{
    TreeNode right = findValueStartingAtNode(node.right_child, value);

    if (right != null && value.CompareTo(right.data) == 0)
    {
        Console.WriteLine("value found in right child");
        return right;
    }
    else
    {
        Console.WriteLine("value not found in either child");
        Console.WriteLine("current node data is {0}", node.data);
        return null;
    }
}

findValueStartingAtNode should be returning either null if the value isn't found or the correct TreeNode if the value is found. There's no reason to do a comparison of the values of the right and left child nodes, because the recursive call on the children nodes will check the value of the children on its own. It will also fail and throw a NullReferenceException whenever a node is encountered without both children. Lastly, your findValue call is searching the tree twice.

I would replace your code with this:

public TreeNode findValue(IComparable value)
{
    TreeNode node = findValueStartingAtNode(this.root, value);
    if (node == null)
    {
        Console.WriteLine("value not found");
    }
    return node;
}

private TreeNode findValueStartingAtNode(TreeNode node, IComparable value)
{
    Console.WriteLine("looking for value {0}", value);

    if (node == null)
    {
        Console.WriteLine("node is null -- returning null");
        return null;
    }
    else if (value.CompareTo(node.data) == 0)
    {
        Console.WriteLine("value found at current node");
        Console.WriteLine("current node data is {0}", node.data);
        Console.WriteLine("done and returning node");
        return node;
    }
    else
    {
        Console.WriteLine("checking left child");
        TreeNode left = findValueStartingAtNode(node.left_child, value);
        if(left != null) return left;

        Console.WriteLine("checking right child");
        TreeNode right = findValueStartingAtNode(node.right_child, value);
        if(right != null) return right;

        Console.WriteLine("value not found in either child");
        Console.WriteLine("current node data is {0}", node.data);
        return null;
    }
}

It should also be mentioned that this doesn't qualify as a binary search tree. It will perform a depth-first search, ending only when the value is found or the entire tree has been visited.

That seems...complicated to me, what with the double calls and all.

It's important to note that every node of a tree is itself a tree. There's no difference between the root node of the tree and any other node of the tree. You just have to do the walk once. Like many recursive problems, the tree walk has two cases:

  • The special case: The tree is empty ( null ).
  • The general case: The tree is non-empty.

Consequently, the tree walk logic is this:

  • The Special Case. If the root (current) node is null, the search failed. return null.

  • The General Case. Compare the root (current) node's payload to the desired value.

    • EQUAL: A Hit! Return the root (current) node.
    • LESS THAN: A miss. Return the results of visiting the left child.
    • GREATER THAN: A miss. Return the results visiting the right child.

That's all there is to it. Given a node structure like this:

public class TreeNode<T> where T:IComparable
{
  public TreeNode<T> Left    { get ; set ; }
  public TreeNode<T> Right   { get ; set ; }
  public T           Payload { get ; set ; }
}

The tree search code shouldn't be much more difficult than this

public TreeNode<T> FindNodeWithValue( IComparable value )
{
  Func<IComparable,int> compareToDesiredValue = (x) => x.CompareTo(value) ;
  TreeNode<T>           node                  = FindNodeWithValue( this.Root , compareToDesiredValue ) ;
  return node ;
}

private TreeNode<T> FindNodeWithValue( TreeNode<T> root , Func<IComparable,int> compareToDesiredValue )
{
  TreeNode<T> result = null ;

  if ( root != null )
  {
    int cc = compareToDesiredValue( root.Payload ) ;
    switch ( cc )
    {
    case 0  : result = root                                                    ; break ;
    case -1 : result = FindNodeWithValue( root.Left  , compareToDesiredValue ) ; break ;
    case +1 : result = FindNodeWithValue( root.Right , compareToDesiredValue ) ; break ;
    default : throw new InvalidOperationException() ;
    }
  }
  return result ;
}

Further, given that this search is directed , so you've got no need to keep track of parent nodes and no need to be able to backtrack on alternatives, the code can easily be iterative and as simple as this:

public TreeNode<T> FindNodeWithValue( IComparable value )
{
  TreeNode<T> current = this.Root ;
  int          cc     ;

  while ( current != null && 0 != (cc=current.Payload.CompareTo(value)) )
  {
    // if we land here, current node does not have our desired value.
    // follow the appropriate left or right child.
    current = cc < 0 ? current.Left : current.Right ;
  }

  // once the while loop completes, current holds the result, with
  // null indicating failure and non-null being the subtree containing
  // the desired value as its root.
  return current ;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM