One needs to do deletes. Four situations are possible

Remember that when the node to be deleted has two children, it can either be replaced by the largest node on the left subtree or the smallest node on the right subtree (to maintain binary tree properties).

Delete 30.

If algorithm chooses to take from right subtree, than want smallest value ... from node to delete (30), go right (35) then left left left until null - at replacement (33)

If algorithm chooses to take from the left subtree, then want largest value ... from node to delete (30) go left (25) then right right right until null - at replace (28)


Delete 40 (replacement from right subtree)

Delete 40 (replacement from left subtree)

Remember that when the node to be deleted has two children, it can either be replaced by the largest node on the left subtree or the smallest node on the right subtree (to maintain binary tree properties). This example code uses the left's largest. sigh.

One can do it either way in examples/code as long as we are clear and consistent. Specifically, an algorithm does it one way or another (not back and forth).

Code which replaces a node with two children with largest node on the left subtree:

So, it selects, as the replacement R, the rightmost node in the left subtree. This is the largest node whose data value is less than that of the node to be deleted.

The following is from another book (Page 588 Data Strucutres with C++ (Ford and Topp)) that has defined a FindNode method (to find the node AND its parent). This Delete method does not return anything.

Implementation Discussion

Issues: Note if you are reading these notes and not listening to a lecture:
- this section is "thinking out loud" to consider (and be aware of) possibilities.

  • What should the ADT specification look like?

    What methods and what variables?

    Methods are Search, Find (to provide parent as well), Insert, Delete (maybe Join, Split). Which should be public? which private?

    What should the IVs be?

    Some depends on implementation and where to hold the information. For example, one does a Find so you can use the info in an Insert or Delete. Should the results of a Find be passed back to the calling method or should they be put in IVs?

    This is a question you should always consider. If other methods are going to make use of the variables too, it is better to put them into IVs.

    IVs and CVs are always providing information about the state of the object.

    If other methods need to know this state, let them have it easily (i.e., provide it in IVs). If in a method, the state is changing, maybe it is useful to have this information. (Example, I am searching the binary tree and am at node curNode...whatever)

    In other languages one did not need to think about this, but suppose someone spawned a bunch of threads to do Inserts (or Deletes) in parallel. Trouble.

    Solution? Of course, synchronize. But remember, "when one thread enters a synchronized method, Java guarantees that it can finish it before another thread can execute any synchronized method in the same object." Here, our bst can block threads and notify them when it becomes available so it is behaving as a monitor. (In Java, any object with one or more synchronized methods is a monitor.) Which methods need to be monitored?

    Clearly, Insert and Delete.

    Which methods could be done using parallelism via recursion? Search and Find.

    Note that this makes one see the usefulness of small, single purpose methods. We might want to do the searches with multiple threads, but if they are inside of the Insert or Delete, then the concurrency is useless. Methods that are synchronized should always be as small as possible. It is only the part that deals with changing the critical variable that should be synchronized.

    So, for example, in the binary trees method insert, the high level insert could find where to put the node (and what this nodes parent is) and then call a lower level synchronized part that makes the change.

    Hmmm, suppose a bunch of Finds are done (and waiting) and info has changed since Find was done. sigh. One often sees objects that might be changed while another is being worked on be cloned first. However, that does not work for data integrity, only for the idea of working on what was there when the work was started. How about checking a flag when one enters Find or Search that indicates a change is in the middle of being made (set upon entry into and exit out of Delete/Insert, and wait for it (sleep (is wait only for sychronizedthreads? notifyAll() (which must be in synchronized) notifies all threads to wake up and check some condition... Remember we do not want to synchronize here since we are trying to call recursion.)