I'm required to develop my own binary search tree class for an assignment. It's supposed to sort a data class (User) which contains two strings, a username and a password, and then later be allowed to search it against a text file by the username variable. I tested the files for the assignment using a TreeSet successfully and found that the search will return about 300 positive matches if the whole search file is used. I thought based on my prior work that my binary tree class would work, but no matter what it never finds any matches via its search function, even though I know that's incorrect.
Some info on the code that isn't shown here-
The node (called BinTree) contains a single user class as well as a left and right node, as is normal.
getUserName is a simple accessor which directly retrieves userName from the node's underlying User.
the grow method is initially called on a manually created root node (b = new BinTree([first data from the file reader is entered here])
Since this is a program handling usernames, case sensitivity is important.
Based on the way the driver class is written, the search method does need to only return true if it finds a User with the correct userName or false otherwise.
Here's the (new) grow() method;
public BinTree grow(BinTree root, BinTree newNode)
{
if (root == null)
return newNode;
if (newNode.getUserName().compareTo(root.getUserName()) > 0)
{
if (root.right == null)
root.right = newNode;
else
root.right.grow(root.right, newNode);
}
else
{
if (root.left == null)
root.left = newNode;
else
root.left.grow(root.left, newNode);
}
return root;
}
And here's the (new) search() method;
public boolean search(BinTree r, String s) //search for a username, case sensitive
{
if (r.getUserName().compareTo(s) == 0)
return true;
else if (r.left != null && r.getUserName().compareTo(s) < 0)
return r.left.search(r.left, s);
else if (r.right != null)
return r.right.search(r.right, s);
return false;
}
Like I said, I had done a simpler binary tree before (that only held single int values, not custom data classes) and I've tried various ways of writing these two methods, but I feel like there's one crucial piece of the puzzle I'm just not getting. Thanks in advance for any help.
UPDATE: Thanks to #Diasiare and #c0der for pointing out the obvious issue that my code had typos regarding returns and left/right. I took that and changed the above code to reflect it. Upon running, the program still didn't seem to be working. I then wrote this show() method to print all the usernames stored in the Users of the tree.
public void show(BinTree r)
{
if (r != null)
{
show(r.left);
System.out.println(r.getUserName());
show(r.right);
}
} // show
When I called it after updating and compiling everything else, it did in fact show that the list was populated with usernames in alphabetical order. Here's a small snippet of the output (there's a lot of lines)
ted#wisler.com
teddy#borglum.com
teddy#winkey.com
-->teodoro#harkin.com<--
teodoro#stranford.com
teodoro#talaska.com
teodoro#willette.com
tera#norise.com
tera#strevels.com
terence#lathrum.com
terence#morocco.com
terence#neidig.com
terence#rabago.com
teresa#chauvin.com
teresa#dockus.com
The one singled out with arrows is one I manually searched through the search .txt file for and found. All told, with this new information I've determined that A) grow() is working correctly, as it populates the tree from the file in alphabetical order, and B) search() should be returning at least one match. Therefore, I'm working under the assumption that the problem lies in search() still.
UPDATE2: Here's the context in which I'm calling search() for those interested.
try
{
BufferedReader fromPasswords = new BufferedReader(new FileReader("passwordInput.txt"));
while ((line = fromPasswords.readLine()) != null)
{
System.out.println(line);
if(b.search(b, line))
{
matches++;
}
}
fromPasswords.close();
}
catch (Exception e)
{
System.out.println("Error while searching tree: " + e);
System.exit(0);
}
Your main problem is that the search function doesn't return the result of the recursive calls, it should read something like the following:
public boolean search(BinTree r, String s) //search for a username, case sensitive
{
if (r.getUserName().compareTo(s) == 0)
return true;
else if (r.left != null && r.getUserName().compareTo(s) < 0)
return r.left.search(r.left, s);
else if (r.right != null)
return r.right.search(r.right, s);
return false;
}
I would also like to note that this:
if (root == null)
root = newNode;
Doesn't make a lot of sense. If root is null you end up inserting newNode as a child of itself. Additionally your method has no way to comunicate that the tree was empty. I would recommend that you throw an NullPointerException, or make the method return the new tree. This will help if in the future you want it to be a balanced tree, in which case the root node might change. In that case the solution would look like this:
public BinTree grow(BinTree root, BinTree newNode)
{
if (root == null)
return newNode;
if (newNode.getUserName().compareTo(root.getUserName()) > 0)
{
if (root.right == null)
root.right = newNode;
else
root.right.grow(root.right, newNode);
}
else
{
if (root.left == null)
root.left = newNode;
else
root.left.grow(root.left, newNode);
}
return root;
}
Additionally as c0der notes the line root.left.grow(root.right, newNode); should probably be root.left.grow(root.left, newNode);
Related
I implemented the Set ADT using the standard approach with nodes (TreeNodes) using a binary search tree.
I have the task of implementing it using the same old nodes except that they have an additional boolean field "active", which can "switch on" (setActive(true)) or "switch off" (setActive(false)) the nodes when removing. This keeps removed nodes there, but they are ignored when we implement toString(), which returns the items in the set.
I was able to implement all the methods of the Set ADT apart from removeAny() (that removes anything from the set, essentially "switches it off"). The problem is that I have to find any node that is "switched on". For this, I have to go through each node and check if one is active. I tried to write the code using recursive calls, but got confused about what to return. Here is my attempt (in Java):
public T removeAny() throws Exception {
if (size == 0) {
throw new Exception ("You cannot remove anything since the set is empty!");
}
return removeAnyHelper (root);
}
public T removeAnyHelper (OnOffTreeNode <T> node) {
if (node == null) {
return root.getValue();
}
if (node.getActive() == true) {
size--;
node.setActive(false);
return node.getValue();
}
removeAnyHelper (node.getLeft());
return removeAnyHelper (node.getRight());
}
How can I fix this method? What should I return?
I tried some if-statements to return both removeAnyHelper (node.getLeft()) and removeAnyHelper (node.getRight()), but this didn't work.
If the node is null, you presumably need to return null (since nothing was removed).
If the recursive call on the left child doesn't return null, it means we removed a node and we need to return it. Otherwise we need to return the result of the call on the right child.
Turning that into code:
public T removeAnyHelper (OnOffTreeNode <T> node) {
if (node == null) {
return null;
}
if (node.getActive()) { // == true is unnecessary
size--;
node.setActive(false);
return node.getValue();
}
T removedNode = removeAnyHelper (node.getLeft());
if (removedNode != null)
return removedNode;
else
return removeAnyHelper (node.getRight());
}
Although I wouldn't really recommend using active/inactive flags for nodes in a BST - deleting a node isn't particularly hard to implement and avoids the problem of having a large number of inactive nodes.
More generally speaking, there are also self-balancing BST's, which avoid reduced efficiency due to unbalanced trees.
Well I don't think removeAny is a proper method for a Set, but anyway.
You can (and probably should) removeAny by removing the first leaf:
private T removeAnyHelper(OnOffTreeNode <T> node) {
if (!present(node.getLeft() && !present(node.getRight())) {
// no children -> this is a leaf, return
node.setActive(false);
return node.getValue();
}
// return left first, or right if left not present
if (present(getLeft())) {
return removeAnyHelper(getLeft());
}
return removeAnyHelper(getRight());
}
private boolean present(OnOffTreeNode<T> node) {
return node != null || node.isActive();
}
Note that you also have a mistake in your base removeAny method because it doesn't check for root's active state:
public T removeAny() throws NoSuchElementException {
if (size == 0 || !root.isActive()) {
throw new NoSuchElementException ("cant remove");
}
return removeAnyHelper(root);
}
Here's the code I have so far, I thought returning it out of the function if my condition wasn't met would work but its stack overflowing...I know that I need to establish a base case but not really how....
public void removeOddSubtrees() {
if (root == null) {
return;
}
removeOddSubtrees(root);
}
private void removeOddSubtrees(Node root) {
removeOddSubtrees(root);
if (root.key % 2 != 0) {
root.right = null;
root.left = null;
root = null;
} else {
return;
}
}
i changed my helper function to the following, and i think it may be working now:
private void removeOddSubtrees(Node root){
if(root != null){
removeOddSubtrees(root.left);
removeOddSubtrees(root.right);
if(root.key % 2 != 0){
root.right = null;
root.left = null;
root = null;
}else{
return;
}
}
}
the code that you posted in the answer does work, but there are some simplifications that can be applied to it. Here's how I'd write it:
private void removeOddSubtrees(Node root) {
if (root == null) {
return;
}
if (root.key % 2 != 0) {
root.right = null;
root.left = null;
return;
}
removeOddSubtrees(root.left);
removeOddSubtrees(root.right);
}
First, I am usually checking the exit condition at the very top and exit the method immediately. That is, my condition does a return if root == null.
Second, there is no need to do root = null if root.key % 2 != 0. it actually has no effect: it places null in the parameter that the function receives but as this parameter is not used after that in this method then no one will ever see this null. Note that also the calling code will not be affected. assignment to parameters do not propagate outside of the called method.
Finally, I think it makes more sense to call removeOddSubtrees() on root.left and root.right only if the key is even. when the key is odd you are removing the left and right subtrees from the tree so doing a recursive call on these subtrees is probably meaningless as this entire subtree will be removed from the tree shortly after. Thus, in my code I do the recursive calls only if the key is even.
So in my binary search tree, I'm trying to test my delete method to see if it removes a node from the BST. The problem is my test keeps saying that it didn't work.
Here's how I'm testing my delete method
message = "Test 3: deleting 'word' -- ";
t = new BSTRefBased();
try {
t.delete("word");
result = t.getRootItem().getWord().equals(null);
} catch (Exception e) {
result = false;
}
System.out.println(message + (result ? "passed" : "failed"));
Here's my delete method:
public void delete(String word) {
root = deleteItem(root, word);
}
protected TreeNode deleteItem(TreeNode r, String word) {
if (r == null){
return r;
}
if(word.compareTo(r.item.getWord()) < 0){
return r;
} else if (word.compareTo(r.item.getWord()) > 0) {
return r;
} else if(r.left != null && r.right != null)
{
return deleteItem(r, word);
} else {
return r;
}
return r;
}
So why does it keep saying that my delete method failed in my output? Is the problem with my test code or with the actual method? Also I did previously insert the word 'word' in my BST so it should be there.
Here's a pseudo code version of what I want my delete method to do:
delete(treeNode ,searchitem)
targetNode = search(treeNode ,searchItem)
if targetNode is null
return
P = parent node of target Node
if targetNode has no children
update ref in P that leads to targetNode
return
if targetNode has only one child C update ref in P that leads
to targetNode by overwriting that ref with C
(either left- or right-ref in P)
return
M = targetNode's inorder successor (i.e., left-most in-order
successor in targetNode's right subtree)
m = item in M
copy m into targetNode's item field
delete (treeNode, M)
return
Assuming the code for your BST is written correctly, explain to me how you are actually deleting the node? When I look at your code, your call to deleteItem(root, word) doesn't do anything at all. Regardless of what happens, it will return the root, which will then be applied to root.
You deleteItem() method never removes the node from the tree. That's why it fails.
Check this answer and the Wikipedia article on Binary Search Trees to better understand how deletions should be done.
I made a binary search tree in Java but I'm having troubles whit the deleting nodes part. I managed to erase the node when it has only 1 son, and I have the idea to make the deletion when it has 2 sons, anyways the method I'm using when it has no sons (when it's a leaf) is not working in Java. Normally in C++ I would assign the Node "null" but it doesn't work here.
if (numberOfSons(node) == 0) {
node= null;
return true;
}
That's the portion of the code that takes care of the nulling part. When I debug it, it is referencing the correct node and it's assigning it the null value, but when I return to the Frame where I'm calling the delete method for my tree the node is still there. What's the correct way to "null" an object in Java? I thought everything was a pointer in here and therefore this would work, but I think it doesn't.
When you're nulling something you just make the reference in the scope you're in null. It doesn't affect anything outside.
Let me explain by example. Say you have a method foo:
public void foo(Node node) {
node = null;
if(node == null) {
System.out.println("node is null");
} else {
System.out.println("node is not null");
}
}
Now you call it like this:
public void doSomething() {
Node node = new Node();
foo(node);
if(node == null) {
System.out.println("Original node is null");
} else {
System.out.println("Original node is not null");
}
}
In your console you'll get:
node is null
original node in not null
The reason is that it's not a pointer, it's a reference. When you're nulling a reference, you just say "make this reference synonym to null". It doesn't mean that the object is deleted, it may still exist in other places. There is no way to delete objects in java. All you can do is make sure no other object points to them, and the garbage collector will delete the objects (sometime).
Nothing remains but to reinsert either left or right subtree. For instance:
class BinaryTree<T extends Comparable<T>> {
class Node {
Node left;
Node right;
T value;
}
Node root;
void delete(T soughtValue) {
root = deleteRec(root, soughtValue);
}
Node deleteRec(Node node, T soughtValue) {
if (node == null) {
return null;
}
int comparison = soughtValue.compareTo(node.value);
if (comparison < 0) {
node.left = deleteRec(node.left, soughtValue);
} else if (comparison > 0) {
node.right = deleteRec(node.right, soughtValue);
} else {
if (node.left == null) {
return node.right;
} else if (node.right == null) {
return node.left;
} else {
// Two subtrees remain, do for instance:
// Return left, with its greatest element getting
// the right subtree.
Node leftsRightmost = node.left;
while (leftsRightmost.right != null) {
leftsRightmost = leftsRightmost.right;
}
leftsRightmost.right = node.right;
return node.left;
}
}
return node;
}
}
As Java does not have aliases parameters as in C++ Node*& - a kind of in-out parameter, I use the result of deleteRec here. In java any function argument that is an object variable will never change the variable with another object instance. That was one of the language design decisions like single inheritance.
I tried to convert a List from 3{1{,2{,}},5{4{,},6{,}}}
to a Binary Tree like this
3
1 5
2 4 6
I thought it would be easier to use recursion but I get stuck.
public void ListToTree (ArrayList al) {
Iterator it = al.iterator();
// n is the Tree's root
BSTnode n = new BSTnode(it.next());
recurse(al,it,n);
}
void recurse (ArrayList al, Iterator it, BSTnode n) {
if(!it.hasNext()) return;
Object element = it.next();
if(element=="{"){
recurse(al,it,n.left());
return;
} else if (element==",") {
recurse(al,it,n.right());
return;
} else if (element =="}") {
}
}
I don't know how to proceed and was wondering if it's the right track. Please give me some hints how to solve it. Moreover, I realize I often get stuck on recursive questions. Is it because I always want to break it down? Should I just think top-down and double-check if it's correct? Thanks in advance !
Firstly: are you bound to that terrible list representation? You can easily build a BST based on the BST rules with this code:
void insert(Node n, int value) {
if(n == null) {
n = new Node(value);
} else if(value < n.value) {
if(n.left == null) {
n.left = new Node(value);
return;
}
insert(n.left, value);
} else if(value > n.value) {
if(n.right == null) {
n.right = new Node(value);
return;
}
insert(n.right, value);
}
}
You really don't have to pass the iterator. Just use the values from the list. Also it is usually unadvised to use implementation types in method signatures. (i.e. ArrayList -> List).
Another big mistake here is that you don't use == for value comparison, that is for reference comparison. Use equals instead, but you should downcast the Object after an instanceof test e.g.:
if( element instanceof String) {
String seperator = (String)element;
if("{".equals(separator))
//do sth...
Btw the thing you are missing from the code is the actual insertion and the backwards navigation.
After you found the right subtree by navigating with the {-s and ,-s, check whether the element is an Integer then set it as a value for the current node. Backwards navigation should be in the } branch by either returning one level from the recusion and some tricks or calling the method on the parent of the actual node.
But I don't suggest you to follow this direction, it is much easier to just use the values from the list and the simple insertion method.